@content-island/vscode-api-client 0.2.3 → 0.2.5
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 +98 -1
- package/dist/index.js +56 -55
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -5,12 +5,30 @@ 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>;
|
|
24
|
+
/**
|
|
25
|
+
* Pulls a fresh snapshot via the configured `snapshotLoader`, validates and guards it, and atomically
|
|
26
|
+
* swaps the active snapshot when the incoming one is newer. Returns `{ status: 'updated', meta }` on
|
|
27
|
+
* adoption or `{ status: 'unchanged', meta }` when the anti-regression guard retains the current one.
|
|
28
|
+
* Throws `ApiClientError` in `'api'` mode, without a configured `snapshotLoader`, or on a
|
|
29
|
+
* loader/validation/identity failure (the active snapshot is left untouched).
|
|
30
|
+
*/
|
|
31
|
+
refreshSnapshot: () => Promise<SnapshotRefreshResult>;
|
|
14
32
|
/**
|
|
15
33
|
* Updates (or upserts, when selected by `fieldName`+`language`) a field value on an existing content.
|
|
16
34
|
*
|
|
@@ -58,6 +76,8 @@ export declare type ClientFilter<Type = string | boolean | number> = Type | {
|
|
|
58
76
|
nin?: Type[];
|
|
59
77
|
};
|
|
60
78
|
|
|
79
|
+
declare type ClientMode = 'api' | 'snapshot';
|
|
80
|
+
|
|
61
81
|
export declare interface Content {
|
|
62
82
|
id: string;
|
|
63
83
|
name: string;
|
|
@@ -73,6 +93,17 @@ export declare type ContentListSizeQueryParams<M extends Model = Model & Record<
|
|
|
73
93
|
|
|
74
94
|
export declare type ContentQueryParams<M extends Model = Model & Record<string, any>> = Omit<QueryParams<M>, 'sort' | 'pagination'>;
|
|
75
95
|
|
|
96
|
+
/**
|
|
97
|
+
* The full content snapshot document produced by `GET /project/export` and consumed
|
|
98
|
+
* by the api-client in static mode. `contents` is serialized in the existing API
|
|
99
|
+
* `Content` shape at related-content depth 0.
|
|
100
|
+
*/
|
|
101
|
+
declare interface ContentSnapshot {
|
|
102
|
+
meta: SnapshotMeta;
|
|
103
|
+
project: Project;
|
|
104
|
+
contents: Content[];
|
|
105
|
+
}
|
|
106
|
+
|
|
76
107
|
declare interface ContentTypeField extends Lookup {
|
|
77
108
|
type: FieldType;
|
|
78
109
|
tsType: string;
|
|
@@ -420,6 +451,26 @@ declare interface Options {
|
|
|
420
451
|
secureProtocol?: boolean;
|
|
421
452
|
apiVersion?: string;
|
|
422
453
|
metadata?: string;
|
|
454
|
+
/**
|
|
455
|
+
* Client-level mode. `'api'` (default) performs network requests against the B2B API;
|
|
456
|
+
* `'snapshot'` serves reads from a snapshot file loaded from `snapshotPath`.
|
|
457
|
+
*/
|
|
458
|
+
mode?: ClientMode;
|
|
459
|
+
/**
|
|
460
|
+
* Path to a content snapshot file (produced by `content-island export`). Optional and cwd-relative;
|
|
461
|
+
* defaults to `DEFAULT_SNAPSHOT_PATH` — the same path the CLI writes by default, so export followed
|
|
462
|
+
* by `createClient({ mode: 'snapshot' })` resolves the same file with zero config. Independent of
|
|
463
|
+
* `mode`: an `'api'` client may still set it to expose `getSnapshotInfo()`. A missing/unreadable/
|
|
464
|
+
* invalid file at the resolved path rejects with an `ApiClientError` from the loader.
|
|
465
|
+
*/
|
|
466
|
+
snapshotPath?: string;
|
|
467
|
+
/**
|
|
468
|
+
* Optional loader for a refreshable snapshot in `'snapshot'` mode. When set without `snapshotPath`
|
|
469
|
+
* (loader-only), the client resolves the active snapshot via the loader on first read. Required for
|
|
470
|
+
* `refreshSnapshot()`; calling `refreshSnapshot()` without it throws an `ApiClientError`. A
|
|
471
|
+
* `snapshotPath`-only client (no loader) keeps the static 0.1.0 behavior.
|
|
472
|
+
*/
|
|
473
|
+
snapshotLoader?: SnapshotLoader;
|
|
423
474
|
}
|
|
424
475
|
|
|
425
476
|
declare type Pagination = {
|
|
@@ -437,6 +488,26 @@ export declare interface Project {
|
|
|
437
488
|
declare type QueryParams<M extends Model = Model & Record<string, any>> = FilterableFields<M> & {
|
|
438
489
|
sort?: SortableFields<M>;
|
|
439
490
|
pagination?: Pagination;
|
|
491
|
+
/**
|
|
492
|
+
* Per-query mode override (effective mode = per-query `mode` ?? client-level `mode` ?? `'api'`).
|
|
493
|
+
* Client-only key — never serialized into the query string.
|
|
494
|
+
*/
|
|
495
|
+
mode?: ClientMode;
|
|
496
|
+
/**
|
|
497
|
+
* Called exactly once with the related-content resolution metadata; when omitted, behavior and
|
|
498
|
+
* return shapes are unchanged. Client-only key — never serialized into the query string.
|
|
499
|
+
*/
|
|
500
|
+
onRelatedContentMeta?: (meta: RelatedContentMeta) => void;
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Related-content resolution metadata for a read: how deep the BFS resolved (`resolvedDepth`) and
|
|
505
|
+
* whether a depth/budget cap left the graph `partial`. Identical values in both modes for the same
|
|
506
|
+
* data and query (header-sourced in api mode, engine-sourced in snapshot mode).
|
|
507
|
+
*/
|
|
508
|
+
declare type RelatedContentMeta = {
|
|
509
|
+
resolvedDepth: number;
|
|
510
|
+
partial: boolean;
|
|
440
511
|
};
|
|
441
512
|
|
|
442
513
|
declare type RelatedModelType = `${string}|${string}`;
|
|
@@ -446,6 +517,32 @@ declare interface SaveModelResponse {
|
|
|
446
517
|
id: string;
|
|
447
518
|
}
|
|
448
519
|
|
|
520
|
+
/**
|
|
521
|
+
* User-provided fetch for a refreshable snapshot. Opaque to the core: it may read a blob/S3/file or
|
|
522
|
+
* call `exportSnapshot()`. Returns either raw snapshot JSON text (which the client `JSON.parse`s) or
|
|
523
|
+
* an already-parsed `ContentSnapshot` object (passed through as-is). Invoked only on the first read in
|
|
524
|
+
* the loader-only case and on `refreshSnapshot()` — never during `createClient` construction.
|
|
525
|
+
*/
|
|
526
|
+
declare type SnapshotLoader = () => Promise<string | ContentSnapshot>;
|
|
527
|
+
|
|
528
|
+
declare interface SnapshotMeta {
|
|
529
|
+
schemaVersion: number;
|
|
530
|
+
/** ISO-8601 timestamp of when the snapshot was generated on the server. */
|
|
531
|
+
exportedAt: string;
|
|
532
|
+
projectId: string;
|
|
533
|
+
view: 'published' | 'preview';
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Outcome of a `refreshSnapshot()` call. `'updated'` when the incoming snapshot was adopted as the new
|
|
538
|
+
* active snapshot (its `meta` is returned); `'unchanged'` when the anti-regression guard kept the
|
|
539
|
+
* current active snapshot (the retained snapshot's `meta` is returned).
|
|
540
|
+
*/
|
|
541
|
+
declare type SnapshotRefreshResult = {
|
|
542
|
+
status: 'updated' | 'unchanged';
|
|
543
|
+
meta: SnapshotMeta;
|
|
544
|
+
};
|
|
545
|
+
|
|
449
546
|
declare type SortableFields<M extends Model> = {
|
|
450
547
|
contentType?: SortOrder;
|
|
451
548
|
lastUpdate?: SortOrder;
|
|
@@ -466,7 +563,7 @@ declare interface UploadMediaParams {
|
|
|
466
563
|
|
|
467
564
|
declare type Validation = { name: string; customArgs?: any };
|
|
468
565
|
|
|
469
|
-
export declare interface VSCodeApiClient extends Omit<ApiClient, 'createModel' | 'updateModel' | 'deleteModel' | 'createEnum' | 'updateEnum' | 'deleteEnum'> {
|
|
566
|
+
export declare interface VSCodeApiClient extends Omit<ApiClient, 'createModel' | 'updateModel' | 'deleteModel' | 'createEnum' | 'updateEnum' | 'deleteEnum' | 'refreshSnapshot'> {
|
|
470
567
|
setVSCodeExtensionContext: (context: vscode.ExtensionContext) => void;
|
|
471
568
|
authorize: (authorizationCode: string, metadata: string) => Promise<void>;
|
|
472
569
|
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
|
|
6
|
-
let
|
|
7
|
-
const
|
|
8
|
-
if (!
|
|
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
|
|
11
|
-
},
|
|
12
|
-
|
|
10
|
+
return C;
|
|
11
|
+
}, f = (t) => {
|
|
12
|
+
C = t;
|
|
13
13
|
};
|
|
14
14
|
let l = {
|
|
15
|
-
getContext:
|
|
16
|
-
setContext:
|
|
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
|
-
},
|
|
24
|
-
SALT: `${
|
|
25
|
-
ACCESS_TOKEN_BY_PROJECT_ID: (t) => `${
|
|
26
|
-
METADATA_BY_PROJECT_ID: (t) => `${
|
|
27
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
38
|
-
const
|
|
39
|
-
return `${
|
|
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
|
|
42
|
-
return `${
|
|
43
|
-
}, y = 16, O = (t = y) => _.randomBytes(t).toString("hex"), x = 64, D = "sha512", m = 1e5, N = async (t, o,
|
|
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
|
-
},
|
|
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
|
-
},
|
|
59
|
+
}, s = (t, o) => async (...a) => {
|
|
60
60
|
try {
|
|
61
|
-
return await t(...
|
|
61
|
+
return await t(...a);
|
|
62
62
|
} catch (e) {
|
|
63
63
|
if (I(e)) {
|
|
64
64
|
if (e.status === 401)
|
|
65
|
-
throw await
|
|
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,
|
|
71
|
-
const e =
|
|
70
|
+
}, U = async (t, o, a) => {
|
|
71
|
+
const e = L(t);
|
|
72
72
|
let n;
|
|
73
|
-
const
|
|
73
|
+
const r = {
|
|
74
74
|
authorizationCode: o,
|
|
75
|
-
metadata:
|
|
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(
|
|
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
|
|
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
|
|
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}`,
|
|
106
|
+
const o = `PREVIEW_${t.accessToken}`, a = A({ ...t, accessToken: o });
|
|
107
107
|
return {
|
|
108
|
-
...
|
|
108
|
+
...a,
|
|
109
109
|
getProject: async () => {
|
|
110
|
-
const e = await
|
|
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
|
|
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
|
|
125
|
-
if (
|
|
126
|
-
|
|
124
|
+
const r = await U(t, e, n);
|
|
125
|
+
if (r) {
|
|
126
|
+
a(r, n);
|
|
127
127
|
const i = await o.getProject();
|
|
128
|
-
await
|
|
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
|
|
133
|
-
if (!n || !
|
|
134
|
-
await
|
|
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
|
-
|
|
137
|
+
a(n, r);
|
|
138
138
|
},
|
|
139
139
|
setVSCodeExtensionContext: (e) => {
|
|
140
140
|
l.setContext(e);
|
|
141
141
|
},
|
|
142
|
-
getProject: (...e) =>
|
|
143
|
-
getContentList: (...e) =>
|
|
144
|
-
getContent: (...e) =>
|
|
145
|
-
getRawContentList: (...e) =>
|
|
146
|
-
getRawContent: (...e) =>
|
|
147
|
-
getContentListSize: (...e) =>
|
|
148
|
-
|
|
149
|
-
|
|
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) =>
|
|
153
|
-
createContent: (...e) =>
|
|
154
|
-
publishContent: (...e) =>
|
|
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
|
+
"version": "0.2.5",
|
|
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.
|
|
35
|
+
"@content-island/api-client": "0.24.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@content-island/common-backend": "*",
|