@content-island/vscode-api-client 0.2.3 → 0.2.4

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/dist/index.d.ts CHANGED
@@ -5,12 +5,22 @@ declare type AllowedFields<M extends Model, Type extends 'sort' | 'filter'> = Pa
5
5
  }, 'fields.id' | 'fields.language'>>;
6
6
 
7
7
  declare interface ApiClient {
8
+ /**
9
+ * Resolves the project. Routed by the client-level mode only (no per-query override): served from
10
+ * the snapshot's `project` block in `'snapshot'` mode, from the API in `'api'` mode.
11
+ */
8
12
  getProject: () => Promise<Project>;
9
13
  getContentList: <M extends Model = Model & Record<string, any>>(queryParam?: ContentListQueryParams<M>) => Promise<M[]>;
10
14
  getContent: <M extends Model = Model & Record<string, any>>(queryParam: ContentQueryParams<M>) => Promise<M>;
11
15
  getRawContentList: <M extends Model = Model & Record<string, any>>(queryParam?: ContentListQueryParams<M>) => Promise<Content[]>;
12
16
  getRawContent: <M extends Model = Model & Record<string, any>>(queryParam: ContentQueryParams<M>) => Promise<Content>;
13
17
  getContentListSize: <M extends Model = Model & Record<string, any>>(queryParam?: ContentListSizeQueryParams<M>) => Promise<number>;
18
+ /**
19
+ * Resolves the snapshot's `meta` block, lazily loading the snapshot regardless of mode (from
20
+ * `snapshotPath`, or `DEFAULT_SNAPSHOT_PATH` when omitted). Rejects with an `ApiClientError` from
21
+ * the loader on a missing/unreadable/invalid file.
22
+ */
23
+ getSnapshotInfo: () => Promise<SnapshotMeta>;
14
24
  /**
15
25
  * Updates (or upserts, when selected by `fieldName`+`language`) a field value on an existing content.
16
26
  *
@@ -58,6 +68,8 @@ export declare type ClientFilter<Type = string | boolean | number> = Type | {
58
68
  nin?: Type[];
59
69
  };
60
70
 
71
+ declare type ClientMode = 'api' | 'snapshot';
72
+
61
73
  export declare interface Content {
62
74
  id: string;
63
75
  name: string;
@@ -420,6 +432,19 @@ declare interface Options {
420
432
  secureProtocol?: boolean;
421
433
  apiVersion?: string;
422
434
  metadata?: string;
435
+ /**
436
+ * Client-level mode. `'api'` (default) performs network requests against the B2B API;
437
+ * `'snapshot'` serves reads from a snapshot file loaded from `snapshotPath`.
438
+ */
439
+ mode?: ClientMode;
440
+ /**
441
+ * Path to a content snapshot file (produced by `content-island export`). Optional and cwd-relative;
442
+ * defaults to `DEFAULT_SNAPSHOT_PATH` — the same path the CLI writes by default, so export followed
443
+ * by `createClient({ mode: 'snapshot' })` resolves the same file with zero config. Independent of
444
+ * `mode`: an `'api'` client may still set it to expose `getSnapshotInfo()`. A missing/unreadable/
445
+ * invalid file at the resolved path rejects with an `ApiClientError` from the loader.
446
+ */
447
+ snapshotPath?: string;
423
448
  }
424
449
 
425
450
  declare type Pagination = {
@@ -437,6 +462,26 @@ export declare interface Project {
437
462
  declare type QueryParams<M extends Model = Model & Record<string, any>> = FilterableFields<M> & {
438
463
  sort?: SortableFields<M>;
439
464
  pagination?: Pagination;
465
+ /**
466
+ * Per-query mode override (effective mode = per-query `mode` ?? client-level `mode` ?? `'api'`).
467
+ * Client-only key — never serialized into the query string.
468
+ */
469
+ mode?: ClientMode;
470
+ /**
471
+ * Called exactly once with the related-content resolution metadata; when omitted, behavior and
472
+ * return shapes are unchanged. Client-only key — never serialized into the query string.
473
+ */
474
+ onRelatedContentMeta?: (meta: RelatedContentMeta) => void;
475
+ };
476
+
477
+ /**
478
+ * Related-content resolution metadata for a read: how deep the BFS resolved (`resolvedDepth`) and
479
+ * whether a depth/budget cap left the graph `partial`. Identical values in both modes for the same
480
+ * data and query (header-sourced in api mode, engine-sourced in snapshot mode).
481
+ */
482
+ declare type RelatedContentMeta = {
483
+ resolvedDepth: number;
484
+ partial: boolean;
440
485
  };
441
486
 
442
487
  declare type RelatedModelType = `${string}|${string}`;
@@ -446,6 +491,14 @@ declare interface SaveModelResponse {
446
491
  id: string;
447
492
  }
448
493
 
494
+ declare interface SnapshotMeta {
495
+ schemaVersion: number;
496
+ /** ISO-8601 timestamp of when the snapshot was generated on the server. */
497
+ exportedAt: string;
498
+ projectId: string;
499
+ view: 'published' | 'preview';
500
+ }
501
+
449
502
  declare type SortableFields<M extends Model> = {
450
503
  contentType?: SortOrder;
451
504
  lastUpdate?: SortOrder;
package/dist/index.js CHANGED
@@ -2,52 +2,52 @@ import { isApiClientError as I, createClient as A } from "@content-island/api-cl
2
2
  import { mapContentToModel as z } from "@content-island/api-client";
3
3
  import * as c from "vscode";
4
4
  import _ from "node:crypto";
5
- import P from "node:util";
6
- let g;
7
- const S = () => {
8
- if (!g)
5
+ import S from "node:util";
6
+ let C;
7
+ const P = () => {
8
+ if (!C)
9
9
  throw new Error("Extension context has not been set.");
10
- return g;
11
- }, L = (t) => {
12
- g = t;
10
+ return C;
11
+ }, f = (t) => {
12
+ C = t;
13
13
  };
14
14
  let l = {
15
- getContext: S,
16
- setContext: L
15
+ getContext: P,
16
+ setContext: f
17
17
  };
18
18
  const d = {
19
19
  IS_PRODUCTION: !0,
20
20
  DEFAULT_API_CLIENT_DOMAIN: "api.contentisland.net",
21
21
  DEFAULT_API_CLIENT_VERSION: "1.0",
22
22
  DEFAULT_LOGIN_DOMAIN: "app.contentisland.net"
23
- }, C = "content-island-vscode", u = {
24
- SALT: `${C}.salt`,
25
- ACCESS_TOKEN_BY_PROJECT_ID: (t) => `${C}.access-token.${t}`,
26
- METADATA_BY_PROJECT_ID: (t) => `${C}.metadata.${t}`
27
- }, E = {
23
+ }, w = "content-island-vscode", u = {
24
+ SALT: `${w}.salt`,
25
+ ACCESS_TOKEN_BY_PROJECT_ID: (t) => `${w}.access-token.${t}`,
26
+ METADATA_BY_PROJECT_ID: (t) => `${w}.metadata.${t}`
27
+ }, h = {
28
28
  get: async (t) => await l.getContext().secrets.get(u.METADATA_BY_PROJECT_ID(t)),
29
29
  set: async (t, o) => {
30
30
  await l.getContext().secrets.store(u.METADATA_BY_PROJECT_ID(t), o);
31
31
  }
32
- }, h = {
32
+ }, E = {
33
33
  get: async (t) => await l.getContext().secrets.get(u.ACCESS_TOKEN_BY_PROJECT_ID(t)),
34
34
  set: async (t, o) => {
35
35
  await l.getContext().secrets.store(u.ACCESS_TOKEN_BY_PROJECT_ID(t), o);
36
36
  }
37
- }, f = (t) => {
38
- const s = (t.secureProtocol === void 0 ? d.IS_PRODUCTION : t.secureProtocol) ? "https" : "http", e = t.domain ? t.domain : d.DEFAULT_API_CLIENT_DOMAIN, n = t.apiVersion ? t.apiVersion : d.DEFAULT_API_CLIENT_VERSION;
39
- return `${s}://${e}/api/${n}`;
37
+ }, L = (t) => {
38
+ const a = (t.secureProtocol === void 0 ? d.IS_PRODUCTION : t.secureProtocol) ? "https" : "http", e = t.domain ? t.domain : d.DEFAULT_API_CLIENT_DOMAIN, n = t.apiVersion ? t.apiVersion : d.DEFAULT_API_CLIENT_VERSION;
39
+ return `${a}://${e}/api/${n}`;
40
40
  }, p = (t) => {
41
- const s = (t.secureProtocol === void 0 ? d.IS_PRODUCTION : t.secureProtocol) ? "https" : "http", e = t.loginDomain ? t.loginDomain : d.DEFAULT_LOGIN_DOMAIN;
42
- return `${s}://${e}/#/?redirect=vscode`;
43
- }, y = 16, O = (t = y) => _.randomBytes(t).toString("hex"), x = 64, D = "sha512", m = 1e5, N = async (t, o, s = x) => (await P.promisify(_.pbkdf2)(t, o, m, s, D)).toString("hex"), k = async () => {
41
+ const a = (t.secureProtocol === void 0 ? d.IS_PRODUCTION : t.secureProtocol) ? "https" : "http", e = t.loginDomain ? t.loginDomain : d.DEFAULT_LOGIN_DOMAIN;
42
+ return `${a}://${e}/#/?redirect=vscode`;
43
+ }, y = 16, O = (t = y) => _.randomBytes(t).toString("hex"), x = 64, D = "sha512", m = 1e5, N = async (t, o, a = x) => (await S.promisify(_.pbkdf2)(t, o, m, a, D)).toString("hex"), k = async () => {
44
44
  const t = l.getContext();
45
45
  let o = await t.secrets.get(u.SALT);
46
46
  return o || (o = O(32), await t.secrets.store(u.SALT, o)), o;
47
47
  }, M = 32, R = async (t) => {
48
48
  const o = await k();
49
49
  return await N(t, o, M);
50
- }, w = async (t) => {
50
+ }, g = async (t) => {
51
51
  const o = "Open Login Page";
52
52
  if (await c.window.showInformationMessage(
53
53
  "You need to log in to Content Island to continue. Do you want to open the login page?",
@@ -56,23 +56,23 @@ const d = {
56
56
  return;
57
57
  const e = c.Uri.parse(p(t));
58
58
  await c.env.openExternal(e);
59
- }, r = (t, o) => async (...s) => {
59
+ }, s = (t, o) => async (...a) => {
60
60
  try {
61
- return await t(...s);
61
+ return await t(...a);
62
62
  } catch (e) {
63
63
  if (I(e)) {
64
64
  if (e.status === 401)
65
- throw await w(o), new Error("Unauthorized: Please log in to continue.");
65
+ throw await g(o), new Error("Unauthorized: Please log in to continue.");
66
66
  e.status === 403 && c.window.showErrorMessage("Access forbidden: You do not have permission to perform this action.");
67
67
  }
68
68
  throw e;
69
69
  }
70
- }, U = async (t, o, s) => {
71
- const e = f(t);
70
+ }, U = async (t, o, a) => {
71
+ const e = L(t);
72
72
  let n;
73
- const a = {
73
+ const r = {
74
74
  authorizationCode: o,
75
- metadata: s
75
+ metadata: a
76
76
  };
77
77
  try {
78
78
  n = await fetch(`${e}/security/vscode/token`, {
@@ -80,7 +80,7 @@ const d = {
80
80
  headers: {
81
81
  "Content-Type": "application/json"
82
82
  },
83
- body: JSON.stringify(a)
83
+ body: JSON.stringify(r)
84
84
  });
85
85
  } catch {
86
86
  c.window.showErrorMessage(
@@ -89,7 +89,7 @@ const d = {
89
89
  return;
90
90
  }
91
91
  if (!n.ok) {
92
- n.status === 401 ? await w(t) : n.status === 403 && c.window.showErrorMessage(
92
+ n.status === 401 ? await g(t) : n.status === 403 && c.window.showErrorMessage(
93
93
  "You do not have permission to access this resource. Please check your access rights in Content Island."
94
94
  ), c.window.showErrorMessage(
95
95
  "Failed to obtain access token. Please complete the authorization in Content Island."
@@ -98,16 +98,16 @@ const d = {
98
98
  }
99
99
  try {
100
100
  const i = await n.json();
101
- return (!i?.accessToken || typeof i.accessToken != "string") && (c.window.showErrorMessage("Invalid response from the authorization server."), await w(t)), i.accessToken;
101
+ return (!i?.accessToken || typeof i.accessToken != "string") && (c.window.showErrorMessage("Invalid response from the authorization server."), await g(t)), i.accessToken;
102
102
  } catch {
103
103
  c.window.showErrorMessage("Error processing response from the authorization server.");
104
104
  }
105
105
  }, T = (t) => {
106
- const o = `PREVIEW_${t.accessToken}`, s = A({ ...t, accessToken: o });
106
+ const o = `PREVIEW_${t.accessToken}`, a = A({ ...t, accessToken: o });
107
107
  return {
108
- ...s,
108
+ ...a,
109
109
  getProject: async () => {
110
- const e = await s.getProject();
110
+ const e = await a.getProject();
111
111
  return {
112
112
  ...e,
113
113
  id: await R(e.id)
@@ -116,42 +116,43 @@ const d = {
116
116
  };
117
117
  }, B = (t = {}) => {
118
118
  let o = T({ ...t, accessToken: "" });
119
- const s = (e, n) => {
119
+ const a = (e, n) => {
120
120
  o = T({ ...t, accessToken: e, metadata: n });
121
121
  };
122
122
  return {
123
123
  authorize: async (e, n) => {
124
- const a = await U(t, e, n);
125
- if (a) {
126
- s(a, n);
124
+ const r = await U(t, e, n);
125
+ if (r) {
126
+ a(r, n);
127
127
  const i = await o.getProject();
128
- await h.set(i.id, a), await E.set(i.id, n);
128
+ await E.set(i.id, r), await h.set(i.id, n);
129
129
  }
130
130
  },
131
131
  authorizeByProjectId: async (e) => {
132
- const n = await h.get(e), a = await E.get(e);
133
- if (!n || !a) {
134
- await w(t);
132
+ const n = await E.get(e), r = await h.get(e);
133
+ if (!n || !r) {
134
+ await g(t);
135
135
  return;
136
136
  }
137
- s(n, a);
137
+ a(n, r);
138
138
  },
139
139
  setVSCodeExtensionContext: (e) => {
140
140
  l.setContext(e);
141
141
  },
142
- getProject: (...e) => r(() => o.getProject(...e), t)(),
143
- getContentList: (...e) => r(() => o.getContentList(...e), t)(),
144
- getContent: (...e) => r(() => o.getContent(...e), t)(),
145
- getRawContentList: (...e) => r(() => o.getRawContentList(...e), t)(),
146
- getRawContent: (...e) => r(() => o.getRawContent(...e), t)(),
147
- getContentListSize: (...e) => r(() => o.getContentListSize(...e), t)(),
148
- updateContentFieldValue: (e, n, a) => r(
149
- () => o.updateContentFieldValue(e, n, a),
142
+ getProject: (...e) => s(() => o.getProject(...e), t)(),
143
+ getContentList: (...e) => s(() => o.getContentList(...e), t)(),
144
+ getContent: (...e) => s(() => o.getContent(...e), t)(),
145
+ getRawContentList: (...e) => s(() => o.getRawContentList(...e), t)(),
146
+ getRawContent: (...e) => s(() => o.getRawContent(...e), t)(),
147
+ getContentListSize: (...e) => s(() => o.getContentListSize(...e), t)(),
148
+ getSnapshotInfo: (...e) => s(() => o.getSnapshotInfo(...e), t)(),
149
+ updateContentFieldValue: (e, n, r) => s(
150
+ () => o.updateContentFieldValue(e, n, r),
150
151
  t
151
152
  )(),
152
- uploadMedia: (...e) => r(() => o.uploadMedia(...e), t)(),
153
- createContent: (...e) => r(() => o.createContent(...e), t)(),
154
- publishContent: (...e) => r(() => o.publishContent(...e), t)()
153
+ uploadMedia: (...e) => s(() => o.uploadMedia(...e), t)(),
154
+ createContent: (...e) => s(() => o.createContent(...e), t)(),
155
+ publishContent: (...e) => s(() => o.publishContent(...e), t)()
155
156
  };
156
157
  };
157
158
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@content-island/vscode-api-client",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Content Island - VSCode Extension API Client",
5
5
  "private": false,
6
6
  "sideEffects": false,
@@ -32,7 +32,7 @@
32
32
  "test:watch": "vitest -c ./config/test/config.ts"
33
33
  },
34
34
  "dependencies": {
35
- "@content-island/api-client": "0.22.0"
35
+ "@content-island/api-client": "0.23.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@content-island/common-backend": "*",