lexmount 0.2.4 → 0.2.6

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 CHANGED
@@ -149,6 +149,14 @@ const details = await client.contexts.get(context.id);
149
149
  console.log(details.status);
150
150
  ```
151
151
 
152
+ Fork an available context into a new context:
153
+
154
+ ```ts
155
+ const source = await client.contexts.create();
156
+ const forked = await client.contexts.fork(source.id);
157
+ console.log(forked.id);
158
+ ```
159
+
152
160
  List contexts:
153
161
 
154
162
  ```ts
@@ -164,6 +172,13 @@ Force release a stuck lock:
164
172
  await client.contexts.forceRelease('context-id');
165
173
  ```
166
174
 
175
+ Current `context fork` limitations:
176
+
177
+ - Only supports offline snapshot copy from an `available` context
178
+ - Does not support hot fork from a `locked` context
179
+ - Does not support copy-on-write
180
+ - Does not support parent/child lineage tracking
181
+
167
182
  ### Extensions
168
183
 
169
184
  Upload an extension archive:
@@ -271,6 +286,7 @@ Example scripts live in [examples](./examples):
271
286
  - `playwright-basic.ts`
272
287
  - `session-management.ts`
273
288
  - `context-basic.ts`
289
+ - `context-fork.ts`
274
290
  - `context-list-get.ts`
275
291
  - `context-modes.ts`
276
292
  - `context-lock-handling.ts`
@@ -283,6 +299,7 @@ Run them with:
283
299
  ```bash
284
300
  cd examples
285
301
  npm install
302
+ npm run context-fork
286
303
  npm run playwright-basic
287
304
  npm run session-management
288
305
  npm run extension-basic
@@ -450,6 +467,14 @@ const details = await client.contexts.get(context.id);
450
467
  console.log(details.status);
451
468
  ```
452
469
 
470
+ 基于一个 `available` context 创建新的 fork:
471
+
472
+ ```ts
473
+ const source = await client.contexts.create();
474
+ const forked = await client.contexts.fork(source.id);
475
+ console.log(forked.id);
476
+ ```
477
+
453
478
  列出 context:
454
479
 
455
480
  ```ts
@@ -465,6 +490,13 @@ const contexts = await client.contexts.list({
465
490
  await client.contexts.forceRelease('context-id');
466
491
  ```
467
492
 
493
+ 当前 `context fork` 第一版限制:
494
+
495
+ - 只支持对 `available context` 做离线快照复制
496
+ - 不支持 `locked` context 的热 fork
497
+ - 不支持 copy-on-write
498
+ - 不支持父子链追踪
499
+
468
500
  ### Extensions
469
501
 
470
502
  上传扩展压缩包:
@@ -572,6 +604,7 @@ npm run docs:html
572
604
  - `playwright-basic.ts`
573
605
  - `session-management.ts`
574
606
  - `context-basic.ts`
607
+ - `context-fork.ts`
575
608
  - `context-list-get.ts`
576
609
  - `context-modes.ts`
577
610
  - `context-lock-handling.ts`
@@ -584,6 +617,7 @@ npm run docs:html
584
617
  ```bash
585
618
  cd examples
586
619
  npm install
620
+ npm run context-fork
587
621
  npm run playwright-basic
588
622
  npm run session-management
589
623
  npm run extension-basic
package/dist/index.d.mts CHANGED
@@ -334,6 +334,10 @@ declare class ContextsResource {
334
334
  * Fetch a single context.
335
335
  */
336
336
  get(contextId: string): Promise<ContextInfo>;
337
+ /**
338
+ * Fork an existing persistent context into a new context.
339
+ */
340
+ fork(contextId: string): Promise<ContextInfo>;
337
341
  /**
338
342
  * Delete a persistent context.
339
343
  */
@@ -505,6 +509,13 @@ declare class SessionsResource {
505
509
  * Create a new browser session.
506
510
  */
507
511
  create(options?: SessionCreateOptions): Promise<SessionCreateResponse>;
512
+ /**
513
+ * Fetch one session by id.
514
+ *
515
+ * The upstream API uses POST /instance/session for this lookup, so this SDK method
516
+ * intentionally uses POST even though the operation is read-only.
517
+ */
518
+ get(sessionId: string, projectId?: string): Promise<SessionInfo>;
508
519
  /**
509
520
  * List sessions for the current project.
510
521
  */
@@ -521,8 +532,11 @@ declare class SessionsResource {
521
532
  _getWebSocketDebuggerUrl(sessionId: string): Promise<string | null>;
522
533
  private normalizeProxy;
523
534
  private handleCreateError;
535
+ private handleGetError;
524
536
  private handleListError;
525
537
  private handleDeleteError;
538
+ private mapSessionInfo;
539
+ private _getCreatedSession;
526
540
  }
527
541
 
528
542
  /**
@@ -657,6 +671,6 @@ declare class TimeoutError extends LexmountError {
657
671
  /**
658
672
  * Lexmount Node.js SDK.
659
673
  */
660
- declare const VERSION = "0.2.4";
674
+ declare const VERSION = "0.2.5";
661
675
 
662
676
  export { APIError, AuthenticationError, type BrowserMode, type ContextAccessMode, type ContextCreateOptions, ContextInfo, type ContextListOptions, ContextListResponse, ContextLockedError, type ContextMetadata, ContextNotFoundError, type ContextStatus, ContextsResource, ExtensionInfo, type ExtensionInfoShape, type ExtensionListOptions, type ExtensionUploadOptions, ExtensionsResource, type ForceReleaseResponse, Lexmount, type LexmountConfig, LexmountError, LexmountLogger, type LogLevel, NetworkError, PaginationInfo, type PaginationInfoShape, type SessionContext, type SessionCreateOptions, type SessionCreateResponse, type SessionDeleteOptions, SessionDownloadInfo, type SessionDownloadInfoShape, SessionDownloadsDeleteResponse, type SessionDownloadsDeleteResponseShape, SessionDownloadsListResponse, type SessionDownloadsListResponseShape, SessionDownloadsResource, SessionInfo, type SessionListOptions, SessionListResponse, SessionNotFoundError, type SessionProxyConfig, type SessionStatus, SessionsResource, TimeoutError, VERSION, ValidationError, Lexmount as default, disableLogging, enableLogging, getLogger, setLogLevel };
package/dist/index.d.ts CHANGED
@@ -334,6 +334,10 @@ declare class ContextsResource {
334
334
  * Fetch a single context.
335
335
  */
336
336
  get(contextId: string): Promise<ContextInfo>;
337
+ /**
338
+ * Fork an existing persistent context into a new context.
339
+ */
340
+ fork(contextId: string): Promise<ContextInfo>;
337
341
  /**
338
342
  * Delete a persistent context.
339
343
  */
@@ -505,6 +509,13 @@ declare class SessionsResource {
505
509
  * Create a new browser session.
506
510
  */
507
511
  create(options?: SessionCreateOptions): Promise<SessionCreateResponse>;
512
+ /**
513
+ * Fetch one session by id.
514
+ *
515
+ * The upstream API uses POST /instance/session for this lookup, so this SDK method
516
+ * intentionally uses POST even though the operation is read-only.
517
+ */
518
+ get(sessionId: string, projectId?: string): Promise<SessionInfo>;
508
519
  /**
509
520
  * List sessions for the current project.
510
521
  */
@@ -521,8 +532,11 @@ declare class SessionsResource {
521
532
  _getWebSocketDebuggerUrl(sessionId: string): Promise<string | null>;
522
533
  private normalizeProxy;
523
534
  private handleCreateError;
535
+ private handleGetError;
524
536
  private handleListError;
525
537
  private handleDeleteError;
538
+ private mapSessionInfo;
539
+ private _getCreatedSession;
526
540
  }
527
541
 
528
542
  /**
@@ -657,6 +671,6 @@ declare class TimeoutError extends LexmountError {
657
671
  /**
658
672
  * Lexmount Node.js SDK.
659
673
  */
660
- declare const VERSION = "0.2.4";
674
+ declare const VERSION = "0.2.5";
661
675
 
662
676
  export { APIError, AuthenticationError, type BrowserMode, type ContextAccessMode, type ContextCreateOptions, ContextInfo, type ContextListOptions, ContextListResponse, ContextLockedError, type ContextMetadata, ContextNotFoundError, type ContextStatus, ContextsResource, ExtensionInfo, type ExtensionInfoShape, type ExtensionListOptions, type ExtensionUploadOptions, ExtensionsResource, type ForceReleaseResponse, Lexmount, type LexmountConfig, LexmountError, LexmountLogger, type LogLevel, NetworkError, PaginationInfo, type PaginationInfoShape, type SessionContext, type SessionCreateOptions, type SessionCreateResponse, type SessionDeleteOptions, SessionDownloadInfo, type SessionDownloadInfoShape, SessionDownloadsDeleteResponse, type SessionDownloadsDeleteResponseShape, SessionDownloadsListResponse, type SessionDownloadsListResponseShape, SessionDownloadsResource, SessionInfo, type SessionListOptions, SessionListResponse, SessionNotFoundError, type SessionProxyConfig, type SessionStatus, SessionsResource, TimeoutError, VERSION, ValidationError, Lexmount as default, disableLogging, enableLogging, getLogger, setLogLevel };
package/dist/index.js CHANGED
@@ -360,6 +360,37 @@ var ContextsResource = class {
360
360
  getLogger().info(`Retrieved context '${contextInfo.id}' (status: ${contextInfo.status})`);
361
361
  return contextInfo;
362
362
  }
363
+ /**
364
+ * Fork an existing persistent context into a new context.
365
+ */
366
+ async fork(contextId) {
367
+ const url = `${this.client.baseUrl}/instance/v1/contexts/${contextId}/fork`;
368
+ const payload = {
369
+ api_key: this.client.apiKey,
370
+ project_id: this.client.projectId
371
+ };
372
+ const response = await this.client._post(url, payload);
373
+ if (response.status >= 400) {
374
+ this.handleError(response, "fork", contextId);
375
+ }
376
+ const result = asRecord(response.data);
377
+ const forkedContextId = getString(result.context_id);
378
+ if (!forkedContextId) {
379
+ throw new APIError("Failed to fork context: context_id missing from response", {
380
+ statusCode: response.status,
381
+ response: response.data
382
+ });
383
+ }
384
+ const contextInfo = new ContextInfo({
385
+ id: forkedContextId,
386
+ status: normalizeContextStatus(result.locked),
387
+ metadata: result.metadata ?? {},
388
+ createdAt: parseTimestamp(result.created_at),
389
+ updatedAt: parseTimestamp(result.updated_at)
390
+ });
391
+ getLogger().info(`Successfully forked context '${contextId}' -> '${contextInfo.id}'`);
392
+ return contextInfo;
393
+ }
363
394
  /**
364
395
  * Delete a persistent context.
365
396
  */
@@ -406,7 +437,7 @@ var ContextsResource = class {
406
437
  const suffix = contextId ? `: ${contextId}` : "";
407
438
  throw new ContextNotFoundError(`Context not found${suffix}`);
408
439
  }
409
- if (response.status === 409 && errorCode === "context_locked") {
440
+ if (response.status === 409 && (errorCode === "context_locked" || errorCode === "CONTEXT_FORK_SOURCE_LOCKED")) {
410
441
  throw new ContextLockedError(errorMessage, {
411
442
  activeSessionId: getString(metadata.activeSessionId),
412
443
  retryAfter: typeof metadata.retryAfter === "number" ? metadata.retryAfter : void 0
@@ -844,22 +875,40 @@ var SessionsResource = class {
844
875
  });
845
876
  }
846
877
  const containerId = getString3(result.container_id) ?? null;
847
- const wsUrl = await this._getWebSocketDebuggerUrl(sessionId);
848
878
  const projectId = options.projectId ?? this.client.projectId;
879
+ const createdSession = await this._getCreatedSession(sessionId, projectId);
880
+ const wsUrl = await this._getWebSocketDebuggerUrl(sessionId);
849
881
  getLogger().info(`Session created successfully: id=${sessionId}, container_id=${containerId}`);
850
882
  return new SessionInfo({
851
883
  id: sessionId,
852
- status: "active",
884
+ status: createdSession?.status ?? "active",
853
885
  apiKey: this.client.apiKey,
854
886
  projectId,
855
- browserType: options.browserMode ?? "normal",
856
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
857
- inspectUrl: `${this.client.baseUrl}/inspect?session_id=${sessionId}`,
858
- containerId,
859
- ws: wsUrl,
887
+ browserType: createdSession?.browserType ?? (options.browserMode ?? "normal"),
888
+ createdAt: createdSession?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
889
+ inspectUrl: createdSession?.inspectUrl ?? `${this.client.baseUrl}/inspect?session_id=${sessionId}`,
890
+ containerId: createdSession?.containerId ?? containerId,
891
+ ws: wsUrl ?? createdSession?.ws ?? null,
860
892
  client: this.client
861
893
  });
862
894
  }
895
+ /**
896
+ * Fetch one session by id.
897
+ *
898
+ * The upstream API uses POST /instance/session for this lookup, so this SDK method
899
+ * intentionally uses POST even though the operation is read-only.
900
+ */
901
+ async get(sessionId, projectId) {
902
+ const response = await this.client._post(`${this.client.baseUrl}/instance/session`, {
903
+ api_key: this.client.apiKey,
904
+ project_id: projectId ?? this.client.projectId,
905
+ session_id: sessionId
906
+ });
907
+ if (response.status >= 400) {
908
+ this.handleGetError(response, sessionId);
909
+ }
910
+ return this.mapSessionInfo(asRecord3(response.data), projectId);
911
+ }
863
912
  /**
864
913
  * List sessions for the current project.
865
914
  */
@@ -879,22 +928,7 @@ var SessionsResource = class {
879
928
  const result = asRecord3(response.data);
880
929
  const sessionsData = Array.isArray(result.sessions) ? result.sessions : [];
881
930
  const paginationData = asRecord3(result.pagination);
882
- const sessions = sessionsData.map((item) => {
883
- const session = asRecord3(item);
884
- const sessionId = getString3(session.id) ?? "";
885
- return new SessionInfo({
886
- id: sessionId,
887
- status: getString3(session.status) ?? "active",
888
- apiKey: getString3(session.api_key) ?? this.client.apiKey,
889
- projectId: getString3(session.project_id) ?? (options.projectId ?? this.client.projectId),
890
- browserType: getString3(session.browser_type) ?? "normal",
891
- createdAt: getString3(session.created_at) ?? "",
892
- inspectUrl: getString3(session.inspect_url) ?? `${this.client.baseUrl}/inspect?session_id=${sessionId}`,
893
- containerId: getString3(session.container_id) ?? null,
894
- ws: getString3(session.ws) ?? null,
895
- client: this.client
896
- });
897
- });
931
+ const sessions = sessionsData.map((item) => this.mapSessionInfo(asRecord3(item), options.projectId));
898
932
  const pagination = new PaginationInfo({
899
933
  currentPage: getNumber(paginationData.currentPage) ?? 1,
900
934
  pageSize: getNumber(paginationData.pageSize) ?? sessions.length,
@@ -988,6 +1022,24 @@ var SessionsResource = class {
988
1022
  response: response.data
989
1023
  });
990
1024
  }
1025
+ handleGetError(response, sessionId) {
1026
+ const errorData = asRecord3(response.data);
1027
+ const errorMessage = getString3(errorData.error) ?? getString3(errorData.message) ?? "Unknown error";
1028
+ if (response.status === 401) {
1029
+ throw new AuthenticationError(
1030
+ `Authentication failed: ${errorMessage}. Please check your API key and project ID.`
1031
+ );
1032
+ }
1033
+ if (response.status === 404) {
1034
+ throw new SessionNotFoundError(
1035
+ `Session not found: ${errorMessage}. Session ID '${sessionId}' may not exist in this project.`
1036
+ );
1037
+ }
1038
+ throw new APIError(`Failed to get session: ${errorMessage}`, {
1039
+ statusCode: response.status,
1040
+ response: response.data
1041
+ });
1042
+ }
991
1043
  handleListError(response) {
992
1044
  const errorData = asRecord3(response.data);
993
1045
  const errorMessage = getString3(errorData.error) ?? getString3(errorData.message) ?? "Unknown error";
@@ -1019,6 +1071,32 @@ var SessionsResource = class {
1019
1071
  response: response.data
1020
1072
  });
1021
1073
  }
1074
+ mapSessionInfo(raw, projectId) {
1075
+ const sessionId = getString3(raw.session_id) ?? getString3(raw.id) ?? "";
1076
+ return new SessionInfo({
1077
+ id: sessionId,
1078
+ status: getString3(raw.status) ?? "active",
1079
+ apiKey: getString3(raw.api_key) ?? this.client.apiKey,
1080
+ projectId: getString3(raw.project_id) ?? (projectId ?? this.client.projectId),
1081
+ browserType: getString3(raw.browser_type) ?? getString3(raw.browser_mode) ?? "normal",
1082
+ createdAt: getString3(raw.created_at) ?? "",
1083
+ inspectUrl: getString3(raw.inspect_url) ?? `${this.client.baseUrl}/inspect?session_id=${sessionId}`,
1084
+ containerId: getString3(raw.container_id) ?? null,
1085
+ ws: getString3(raw.ws) ?? null,
1086
+ client: this.client
1087
+ });
1088
+ }
1089
+ async _getCreatedSession(sessionId, projectId) {
1090
+ try {
1091
+ return await this.get(sessionId, projectId);
1092
+ } catch (error) {
1093
+ getLogger().warn(
1094
+ `Failed to fetch created session ${sessionId}; falling back to locally derived session fields:`,
1095
+ error
1096
+ );
1097
+ return null;
1098
+ }
1099
+ }
1022
1100
  };
1023
1101
 
1024
1102
  // src/client.ts
@@ -1147,7 +1225,7 @@ var Lexmount = class {
1147
1225
  };
1148
1226
 
1149
1227
  // src/index.ts
1150
- var VERSION = "0.2.4";
1228
+ var VERSION = "0.2.5";
1151
1229
  // Annotate the CommonJS export names for ESM import in node:
1152
1230
  0 && (module.exports = {
1153
1231
  APIError,