@content-island/vscode-api-client 0.2.2 → 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
  *
@@ -32,6 +42,18 @@ declare interface ApiClient {
32
42
  * the live snapshot from the current draft.
33
43
  */
34
44
  publishContent: (contentId: string) => Promise<boolean>;
45
+ /** Creates an Entity model. Rejects with `ApiClientError` on non-2xx. */
46
+ createModel: (params: CreateEntityParams) => Promise<SaveModelResponse>;
47
+ /** Full-upsert of an Entity model: the supplied `fieldList` replaces the stored one. */
48
+ updateModel: (modelId: string, params: UpdateEntityParams) => Promise<SaveModelResponse>;
49
+ /** Deletes a model. Rejects with `ApiClientError` (e.g. 409 when referenced). */
50
+ deleteModel: (modelId: string) => Promise<boolean>;
51
+ /** Creates an Enum model. Rejects with `ApiClientError` on non-2xx. */
52
+ createEnum: (params: CreateEnumParams) => Promise<SaveModelResponse>;
53
+ /** Full-upsert of an Enum model: the supplied `values` replace the stored ones. */
54
+ updateEnum: (enumId: string, params: UpdateEnumParams) => Promise<SaveModelResponse>;
55
+ /** Deletes an enum. Rejects with `ApiClientError` (e.g. 409 when referenced). */
56
+ deleteEnum: (enumId: string) => Promise<boolean>;
35
57
  }
36
58
 
37
59
  declare interface BaseModel {
@@ -46,6 +68,8 @@ export declare type ClientFilter<Type = string | boolean | number> = Type | {
46
68
  nin?: Type[];
47
69
  };
48
70
 
71
+ declare type ClientMode = 'api' | 'snapshot';
72
+
49
73
  export declare interface Content {
50
74
  id: string;
51
75
  name: string;
@@ -87,6 +111,19 @@ declare interface CreateContentResponse {
87
111
  id: string;
88
112
  }
89
113
 
114
+ declare interface CreateEntityParams {
115
+ name: string;
116
+ fieldList: FieldSpec[];
117
+ }
118
+
119
+ declare interface CreateEnumParams {
120
+ name: string;
121
+ values: Array<{
122
+ id?: string;
123
+ value: string;
124
+ }>;
125
+ }
126
+
90
127
  declare interface Entity extends BaseModel {
91
128
  type: 'entity';
92
129
  fieldList?: Field_2[];
@@ -131,6 +168,21 @@ declare type FieldSelector = {
131
168
  language: string;
132
169
  };
133
170
 
171
+ /**
172
+ * Structured field spec. `id` is optional: present = existing field to preserve
173
+ * on update, absent = the server creates it. `relatedModelId` is a 24-char model
174
+ * id used only for `relation`/`enum` types. `order` never appears (server derives
175
+ * it by list position).
176
+ */
177
+ declare interface FieldSpec {
178
+ id?: string;
179
+ name: string;
180
+ type: FieldTypeSpec;
181
+ relatedModelId?: string;
182
+ isArray?: boolean;
183
+ validations?: Validation[];
184
+ }
185
+
134
186
  export declare type FieldType =
135
187
  | 'short-text'
136
188
  | 'long-text'
@@ -142,6 +194,19 @@ export declare type FieldType =
142
194
  | 'color'
143
195
  | RelatedModelType;
144
196
 
197
+ /**
198
+ * Agent-facing field type discriminator (the value an agent supplies for a field's
199
+ * `type` when creating/editing a model). The B2B server translates `relation`/`enum`
200
+ * + `relatedModelId` into the stored `` `${modelId}|${Name}` `` type string; the
201
+ * agent never supplies a composite type string.
202
+ *
203
+ * Derived from common's stored `FieldType` (single source of truth): the 8 primitive
204
+ * tokens (the stored type minus the composite `${id}|${Name}` form) plus the two
205
+ * agent-facing discriminators. Distinct from the read-side `FieldType` (common), which
206
+ * is the composite the server returns on content/project responses.
207
+ */
208
+ declare type FieldTypeSpec = Exclude<FieldType, `${string}|${string}`> | 'relation' | 'enum';
209
+
145
210
  declare type FilterableFields<M extends Model = Model> = {
146
211
  id?: ClientFilter<string>;
147
212
  lastUpdate?: ClientFilter<string>;
@@ -367,6 +432,19 @@ declare interface Options {
367
432
  secureProtocol?: boolean;
368
433
  apiVersion?: string;
369
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;
370
448
  }
371
449
 
372
450
  declare type Pagination = {
@@ -384,10 +462,43 @@ export declare interface Project {
384
462
  declare type QueryParams<M extends Model = Model & Record<string, any>> = FilterableFields<M> & {
385
463
  sort?: SortableFields<M>;
386
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;
387
485
  };
388
486
 
389
487
  declare type RelatedModelType = `${string}|${string}`;
390
488
 
489
+ /** Committed-operation success response for create/update model/enum. */
490
+ declare interface SaveModelResponse {
491
+ id: string;
492
+ }
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
+
391
502
  declare type SortableFields<M extends Model> = {
392
503
  contentType?: SortOrder;
393
504
  lastUpdate?: SortOrder;
@@ -397,6 +508,10 @@ declare type SortOrder = 'asc' | 'desc';
397
508
 
398
509
  declare type Status = 'draft' | 'published' | 'changed';
399
510
 
511
+ declare type UpdateEntityParams = CreateEntityParams;
512
+
513
+ declare type UpdateEnumParams = CreateEnumParams;
514
+
400
515
  declare interface UploadMediaParams {
401
516
  file: Blob | File;
402
517
  fileName?: string;
@@ -404,7 +519,7 @@ declare interface UploadMediaParams {
404
519
 
405
520
  declare type Validation = { name: string; customArgs?: any };
406
521
 
407
- export declare interface VSCodeApiClient extends ApiClient {
522
+ export declare interface VSCodeApiClient extends Omit<ApiClient, 'createModel' | 'updateModel' | 'deleteModel' | 'createEnum' | 'updateEnum' | 'deleteEnum'> {
408
523
  setVSCodeExtensionContext: (context: vscode.ExtensionContext) => void;
409
524
  authorize: (authorizationCode: string, metadata: string) => Promise<void>;
410
525
  authorizeByProjectId: (projectId: string) => Promise<void>;
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.2",
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.21.0"
35
+ "@content-island/api-client": "0.23.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@content-island/common-backend": "*",