liferay-headless-sdk 1.0.2 → 1.0.3

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
@@ -1,6 +1,6 @@
1
1
  # Liferay Headless SDK
2
2
 
3
- A production-ready JavaScript SDK that **dynamically generates** API client methods from Liferay's Swagger/OpenAPI specifications at runtime. Zero manual mapping required — point it at a Liferay instance and call APIs immediately.
3
+ A JavaScript SDK that **dynamically generates** API client methods from Liferay's Swagger/OpenAPI specifications at runtime. Zero manual mapping required — point it at a Liferay instance and call APIs immediately.
4
4
 
5
5
  ---
6
6
 
@@ -17,6 +17,7 @@ A production-ready JavaScript SDK that **dynamically generates** API client meth
17
17
  - [CLI Tool](#cli-tool)
18
18
  - [TypeScript](#typescript)
19
19
  - [Advanced Usage](#advanced-usage)
20
+ - [File Structure](#file-structure)
20
21
 
21
22
  ---
22
23
 
@@ -50,9 +51,9 @@ const client = new LiferayHeadlessClient({
50
51
  // Initialize (loads and parses OpenAPI schemas)
51
52
  await client.init();
52
53
 
53
- // Call generated methods
54
- const { data: sites } = await client.headlessAdminUser.site.getMyUserAccountSitesPage();
55
- console.log(sites.items);
54
+ // Call generated methods — namespace > tag > method
55
+ const { data } = await client.headlessAdminUser.site.getMyUserAccountSitesPage();
56
+ console.log(data.items);
56
57
  ```
57
58
 
58
59
  ---
@@ -72,17 +73,24 @@ const client = new LiferayHeadlessClient({
72
73
  '/o/object-admin/v1.0/openapi.json',
73
74
  ],
74
75
 
76
+ // Filter to specific operation IDs (optional)
77
+ operationIds: [],
78
+
79
+ // Filter to specific tags (optional)
80
+ tags: [],
81
+
75
82
  // Auth — use one of the options below
76
83
  username: 'test@liferay.com',
77
84
  password: 'test',
78
85
  // oauthToken: 'your-bearer-token',
86
+ // authToken: 'your-csrf-token', // sets x-csrf-token header
79
87
 
80
88
  // HTTP behavior
81
89
  timeout: 30000, // ms — default 30s
82
90
  retries: 2, // automatic retries on 5xx / network errors
83
91
 
84
92
  // Lazy init via Proxy (default true)
85
- // Set false if you want to call init() manually and avoid Proxy overhead
93
+ // When true, init() is called automatically on first service access
86
94
  autoGenerate: true,
87
95
  });
88
96
  ```
@@ -95,7 +103,6 @@ const client = new LiferayHeadlessClient({
95
103
  | Headless Admin User | `/o/headless-admin-user/v1.0/openapi.json` |
96
104
  | Headless Admin Content | `/o/headless-admin-content/v1.0/openapi.json` |
97
105
  | Object Admin | `/o/object-admin/v1.0/openapi.json` |
98
- | All APIs (discovery) | `/o/api` |
99
106
 
100
107
  ---
101
108
 
@@ -120,6 +127,15 @@ const client = new LiferayHeadlessClient({
120
127
  });
121
128
  ```
122
129
 
130
+ ### CSRF Token
131
+
132
+ ```js
133
+ const client = new LiferayHeadlessClient({
134
+ baseUrl: '...',
135
+ authToken: 'your-csrf-token', // sets x-csrf-token header
136
+ });
137
+ ```
138
+
123
139
  ### Switching Auth Dynamically
124
140
 
125
141
  ```js
@@ -137,16 +153,18 @@ client.clearAuth();
137
153
 
138
154
  ## Dynamic Method Usage
139
155
 
140
- After `init()`, service namespaces are accessible as properties of the client. The namespace name is derived from the OpenAPI tag (converted to camelCase).
156
+ After `init()`, service namespaces are accessible as `client.<namespace>.<tag>.<method>()`.
157
+
158
+ The namespace is derived from the OpenAPI `info.title` (camelCased). Tags become sub-namespaces.
141
159
 
142
160
  ```js
143
161
  await client.init();
144
162
 
145
163
  // GET /v1.0/sites/{siteId}
146
- const { data } = await client.headlessAdminUser.site.getSite({siteId: 12345});
164
+ const { data } = await client.headlessAdminUser.site.getSite({ siteId: 12345 });
147
165
 
148
166
  // GET /v1.0/structured-contents/{structuredContentId}
149
- const { data: contents } = await client.headlessDelivery.structuredContent.getStructuredContent({
167
+ const { data: content } = await client.headlessDelivery.structuredContent.getStructuredContent({
150
168
  structuredContentId: 999,
151
169
  });
152
170
 
@@ -174,7 +192,7 @@ await client.headlessDelivery.structuredContent.deleteStructuredContent({
174
192
 
175
193
  ### Parameter Mapping
176
194
 
177
- The SDK auto-maps parameters from the single `params` object:
195
+ All parameters are passed as a single flat object:
178
196
 
179
197
  | Param type | How to pass |
180
198
  |------------|-------------|
@@ -183,7 +201,22 @@ The SDK auto-maps parameters from the single `params` object:
183
201
  | Request body | `{ body: { ... } }` |
184
202
  | Extra headers | `{ headers: { 'X-Custom': 'value' } }` |
185
203
 
186
- Any extra keys not defined in the OpenAPI spec are passed as query parameters.
204
+ Any extra keys not defined in the OpenAPI spec fall through as query parameters.
205
+
206
+ ### Filtering Operations
207
+
208
+ Load only what you need by filtering at construction time:
209
+
210
+ ```js
211
+ const client = new LiferayHeadlessClient({
212
+ baseUrl: '...',
213
+ swaggerUrls: ['/o/headless-delivery/v1.0/openapi.json'],
214
+ // Only generate methods for these operation IDs
215
+ operationIds: ['getSite', 'getSites'],
216
+ // Or filter by tag name
217
+ tags: ['Site'],
218
+ });
219
+ ```
187
220
 
188
221
  ### Discovering Available Methods
189
222
 
@@ -192,9 +225,13 @@ Any extra keys not defined in the OpenAPI spec are passed as query parameters.
192
225
  console.log(client.getServiceNames());
193
226
  // ['headlessDelivery', 'headlessAdminUser', ...]
194
227
 
195
- // List methods in a namespace
228
+ // List tag groups within a namespace
229
+ const ns = client._services['headlessDelivery'];
230
+ console.log(Object.keys(ns));
231
+ // ['structuredContent', 'site', ...]
232
+
233
+ // List methods in a tag group
196
234
  console.log(client.getMethodNames('headlessDelivery'));
197
- // ['getSites', 'getStructuredContents', 'createStructuredContent', ...]
198
235
  ```
199
236
 
200
237
  ---
@@ -208,9 +245,7 @@ Liferay returns paginated responses with `{ items, page, pageSize, totalCount, l
208
245
  ```js
209
246
  import { iteratePages } from 'liferay-headless-sdk';
210
247
 
211
- await client.init();
212
-
213
- for await (const site of iteratePages(client.headlessDelivery.getSites, { pageSize: 50 })) {
248
+ for await (const site of iteratePages(client.headlessAdminUser.site.getMyUserAccountSitesPage, { pageSize: 50 })) {
214
249
  console.log(site.name);
215
250
  }
216
251
  ```
@@ -220,7 +255,7 @@ for await (const site of iteratePages(client.headlessDelivery.getSites, { pageSi
220
255
  ```js
221
256
  import { collectAllPages } from 'liferay-headless-sdk';
222
257
 
223
- const allUsers = await collectAllPages(client.headlessAdminUser.getUsers, { pageSize: 100 });
258
+ const allUsers = await collectAllPages(client.headlessAdminUser.site.getMyUserAccountSitesPage, { pageSize: 100 });
224
259
  ```
225
260
 
226
261
  ### Fetch a specific page
@@ -228,7 +263,7 @@ const allUsers = await collectAllPages(client.headlessAdminUser.getUsers, { page
228
263
  ```js
229
264
  import { getPage } from 'liferay-headless-sdk';
230
265
 
231
- const page2 = await getPage(client.headlessDelivery.getSites, 2, 20);
266
+ const page2 = await getPage(client.headlessAdminUser.site.getMyUserAccountSitesPage, 2, 20);
232
267
  console.log(page2.items, page2.totalCount, page2.lastPage);
233
268
  ```
234
269
 
@@ -281,10 +316,9 @@ client.addResponseInterceptor(async (response) => {
281
316
  import { LiferayAPIError, LiferayNetworkError, LiferayTimeoutError } from 'liferay-headless-sdk';
282
317
 
283
318
  try {
284
- const { data } = await client.headlessDelivery.getSites();
319
+ const { data } = await client.headlessAdminUser.site.getMyUserAccountSitesPage();
285
320
  } catch (err) {
286
321
  if (err instanceof LiferayAPIError) {
287
- // HTTP error response from Liferay
288
322
  console.error(`API Error ${err.statusCode}: ${err.message}`);
289
323
  console.error('Endpoint:', err.endpoint);
290
324
  console.error('Response body:', err.responseBody);
@@ -306,7 +340,7 @@ try {
306
340
  | `LiferayNetworkError` | `message`, `endpoint`, `cause` |
307
341
  | `LiferayTimeoutError` | `message`, `endpoint`, `timeoutMs` |
308
342
 
309
- 4xx errors are **not retried**. 5xx and network errors are retried up to `retries` times with exponential backoff.
343
+ 4xx errors are not retried. 5xx and network errors are retried up to `retries` times with exponential backoff.
310
344
 
311
345
  ---
312
346
 
@@ -326,7 +360,7 @@ npx liferay-sdk-cli generate \
326
360
 
327
361
  | Flag | Description | Default |
328
362
  |------|-------------|---------|
329
- | `--baseUrl` | Liferay instance URL | *required* |
363
+ | `--baseUrl` | Liferay instance URL | required |
330
364
  | `--output` | Output directory | `./generated-sdk` |
331
365
  | `--username` | Basic Auth username | |
332
366
  | `--password` | Basic Auth password | |
@@ -341,11 +375,13 @@ npx liferay-sdk-cli generate \
341
375
  --swagger /o/headless-delivery/v1.0/openapi.json,/o/my-custom-api/v1.0/openapi.json
342
376
  ```
343
377
 
378
+ The CLI generates a `services/` directory with one JS file per API, plus an `index.js` that re-exports everything alongside the core SDK helpers.
379
+
344
380
  ---
345
381
 
346
382
  ## TypeScript
347
383
 
348
- Full TypeScript declarations are included. The dynamic service namespaces are typed with an index signature:
384
+ TypeScript declarations are included via `src/index.d.ts`. Dynamic service namespaces are typed with an index signature:
349
385
 
350
386
  ```ts
351
387
  import { LiferayHeadlessClient, LiferayAPIError } from 'liferay-headless-sdk';
@@ -359,12 +395,12 @@ const client = new LiferayHeadlessClient({
359
395
 
360
396
  await client.init();
361
397
 
362
- // Dynamically accessed namespaces return Record<string, ApiMethod>
363
- const service = client['headlessDelivery'] as Record<string, Function>;
364
- const result = await service['getSites']();
398
+ // Dynamically accessed namespaces
399
+ const ns = client['headlessDelivery'] as Record<string, Record<string, Function>>;
400
+ const result = await ns['structuredContent']['getStructuredContents']();
365
401
  ```
366
402
 
367
- For strongly-typed wrappers, use the CLI to generate static service modules with explicit types.
403
+ For strongly-typed wrappers, use the CLI to generate static service modules.
368
404
 
369
405
  ---
370
406
 
@@ -375,7 +411,7 @@ For strongly-typed wrappers, use the CLI to generate static service modules with
375
411
  ```js
376
412
  const client = new LiferayHeadlessClient({
377
413
  baseUrl: '...',
378
- swaggerUrls: [], // Start with no schemas
414
+ swaggerUrls: [],
379
415
  autoGenerate: false,
380
416
  username: 'test@liferay.com',
381
417
  password: 'test',
@@ -383,7 +419,7 @@ const client = new LiferayHeadlessClient({
383
419
 
384
420
  // Load only what you need
385
421
  await client.loadSchema('/o/headless-delivery/v1.0/openapi.json');
386
- const { data } = await client.headlessDelivery.getSites();
422
+ const { data } = await client.headlessDelivery.structuredContent.getStructuredContents();
387
423
  ```
388
424
 
389
425
  ### Raw HTTP access
@@ -409,18 +445,19 @@ await client.init();
409
445
  ## File Structure
410
446
 
411
447
  ```
412
- liferay-sdk/
413
- ├── index.js — Public exports
414
- ├── index.d.ts TypeScript declarations
415
- ├── client.js LiferayHeadlessClient class
416
- ├── swagger-loader.js OpenAPI schema fetching & caching
417
- ├── api-generator.js — Dynamic method generation from schemas
418
- ├── http.js HTTP transport (fetch, retry, timeout)
419
- ├── auth.js — Basic Auth & OAuth2 manager
420
- ├── errors.js LiferayAPIError, LiferayNetworkError, LiferayTimeoutError
421
- ├── pagination.js iteratePages, collectAllPages, getPage
422
- ├── utils.js URL building, camelCase conversion, etc.
423
- ├── cli.js liferay-sdk-cli tool
448
+ liferay-headless-sdk/
449
+ ├── src/
450
+ ├── index.js Public exports
451
+ ├── index.d.ts TypeScript declarations
452
+ ├── client.js LiferayHeadlessClient (main entry point)
453
+ ├── api-generator.js — Parses OpenAPI schemas, generates service modules
454
+ ├── swagger-loader.js Fetches and caches OpenAPI JSON schemas
455
+ ├── http.js — Fetch wrapper with retry, timeout, interceptors
456
+ ├── auth.js Basic Auth, OAuth2, and CSRF token management
457
+ ├── errors.js LiferayAPIError, LiferayNetworkError, LiferayTimeoutError
458
+ ├── pagination.js iteratePages, collectAllPages, getPage
459
+ ├── utils.js URL building, camelCase, query string helpers
460
+ │ └── cli.js — liferay-sdk-cli binary
424
461
  ├── package.json
425
462
  └── README.md
426
463
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liferay-headless-sdk",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "A production-ready JavaScript SDK dynamically generated from Liferay Headless API Swagger/OpenAPI specifications.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -15,7 +15,7 @@ const BODY_METHODS = new Set(['post', 'put', 'patch']);
15
15
  * @param {object} schema - Parsed OpenAPI/Swagger JSON
16
16
  * @returns {Map<string, Array<OperationDef>>} Tag → list of operation definitions
17
17
  */
18
- export function parseOperationsByTag(schema) {
18
+ export function parseOperationsByTag(schema, operationIds, _tags) {
19
19
  const tagMap = new Map();
20
20
  const paths = schema.paths || {};
21
21
  const url = schema.servers[0].url;
@@ -24,12 +24,13 @@ export function parseOperationsByTag(schema) {
24
24
  for (const method of ['get', 'post', 'put', 'patch', 'delete']) {
25
25
  const operation = pathItem[method];
26
26
  if (!operation) continue;
27
+ if (operationIds.length && !operationIds.includes(operation.operationId)) continue;
27
28
 
28
- const tags = (operation.tags && operation.tags.length > 0)
29
- ? operation.tags
30
- : ['default'];
29
+ const tags = operation.tags && operation.tags.length > 0 ? operation.tags : ['default'];
31
30
 
32
31
  const primaryTag = tags[0];
32
+ if (_tags.length && !_tags.includes(primaryTag)) continue;
33
+
33
34
  const tagKey = tagToPropertyName(primaryTag);
34
35
 
35
36
  if (!tagMap.has(tagKey)) {
@@ -80,14 +81,10 @@ export function buildOperationMethod(operation, httpClient) {
80
81
  const { url, method, pathTemplate, operationId, parameters } = operation;
81
82
 
82
83
  // Identify path parameter names
83
- const pathParamNames = new Set(
84
- parameters.filter((p) => p.in === 'path').map((p) => p.name)
85
- );
84
+ const pathParamNames = new Set(parameters.filter((p) => p.in === 'path').map((p) => p.name));
86
85
 
87
86
  // Identify query parameter names
88
- const queryParamNames = new Set(
89
- parameters.filter((p) => p.in === 'query').map((p) => p.name)
90
- );
87
+ const queryParamNames = new Set(parameters.filter((p) => p.in === 'query').map((p) => p.name));
91
88
 
92
89
  /**
93
90
  * Dynamically generated API method.
@@ -157,7 +154,9 @@ export function buildServiceModule(operations, httpClient) {
157
154
  for (const operation of operations) {
158
155
  const methodName = operationIdToMethodName(operation.operationId);
159
156
  if (!methodName) {
160
- console.warn(`[LiferaySDK] Skipping operation with no operationId at ${operation.method.toUpperCase()} ${operation.pathTemplate}`);
157
+ console.warn(
158
+ `[LiferaySDK] Skipping operation with no operationId at ${operation.method.toUpperCase()} ${operation.pathTemplate}`,
159
+ );
161
160
  continue;
162
161
  }
163
162
  if (module[methodName]) {
@@ -179,10 +178,10 @@ export function buildServiceModule(operations, httpClient) {
179
178
  * @param {import('./http.js').HttpClient} httpClient
180
179
  * @returns {Record<string, Record<string, Function>>} Tag → service module
181
180
  */
182
- export function generateServicesFromSchema(schema, httpClient) {
183
- const tagMap = parseOperationsByTag(schema);
181
+ export function generateServicesFromSchema(schema, operationIds, tags, httpClient) {
182
+ const tagMap = parseOperationsByTag(schema, operationIds, tags);
184
183
  const service = tagToPropertyName(schema.info.title);
185
- const services = {[service]:{}};
184
+ const services = { [service]: {} };
186
185
 
187
186
  for (const [tagKey, operations] of tagMap.entries()) {
188
187
  services[service][tagKey] = buildServiceModule(operations, httpClient);
package/src/client.js CHANGED
@@ -43,6 +43,8 @@ export class LiferayHeadlessClient {
43
43
  const {
44
44
  baseUrl = '',
45
45
  swaggerUrls = [],
46
+ operationIds = [],
47
+ tags = [],
46
48
  username,
47
49
  password,
48
50
  oauthToken,
@@ -56,6 +58,8 @@ export class LiferayHeadlessClient {
56
58
 
57
59
  this.baseUrl = baseUrl.replace(/\/$/, '');
58
60
  this._swaggerUrls = swaggerUrls;
61
+ this._operationIds = operationIds;
62
+ this._tags = tags;
59
63
  this._autoGenerate = autoGenerate;
60
64
 
61
65
  // Sub-modules
@@ -99,7 +103,7 @@ export class LiferayHeadlessClient {
99
103
  const schemas = await this._loader.loadAll(this._swaggerUrls, this.baseUrl, authHeaders);
100
104
 
101
105
  for (const { url, schema } of schemas) {
102
- this._mergeServices(generateServicesFromSchema(schema, this._http), url);
106
+ this._mergeServices(generateServicesFromSchema(schema, this._operationIds, this._tags, this._http), url);
103
107
  }
104
108
 
105
109
  this._initialized = true;
@@ -117,7 +121,7 @@ export class LiferayHeadlessClient {
117
121
  this._auth.injectAuthHeaders(authHeaders);
118
122
 
119
123
  const schema = await this._loader.load(swaggerUrl, this.baseUrl, authHeaders);
120
- this._mergeServices(generateServicesFromSchema(schema, this._http), swaggerUrl);
124
+ this._mergeServices(generateServicesFromSchema(schema, this._operationIds, this._tags, this._http), swaggerUrl);
121
125
  }
122
126
 
123
127
  // ─── Auth ──────────────────────────────────────────────────────────────────
package/src/index.d.ts CHANGED
@@ -37,6 +37,7 @@ export type AuthType = 'basic' | 'oauth';
37
37
  export declare class AuthManager {
38
38
  setBasicAuth(username: string, password: string): void;
39
39
  setOAuthToken(token: string): void;
40
+ setAuthToken(authToken: string): void;
40
41
  clearAuth(): void;
41
42
  getAuthHeader(): string | null;
42
43
  getAuthType(): AuthType | null;
@@ -97,11 +98,18 @@ export interface OperationDef {
97
98
  description: string;
98
99
  }
99
100
 
100
- export declare function parseOperationsByTag(schema: object): Map<string, OperationDef[]>;
101
+ export declare function parseOperationsByTag(
102
+ schema: object,
103
+ operationIds?: string[],
104
+ tags?: string[]
105
+ ): Map<string, OperationDef[]>;
106
+
101
107
  export declare function generateServicesFromSchema(
102
108
  schema: object,
109
+ operationIds: string[],
110
+ tags: string[],
103
111
  httpClient: HttpClient
104
- ): Record<string, Record<string, ApiMethod>>;
112
+ ): Record<string, Record<string, Record<string, ApiMethod>>>;
105
113
 
106
114
  // ─── API Method ────────────────────────────────────────────────────────────
107
115
 
@@ -148,12 +156,18 @@ export interface LiferayClientOptions {
148
156
  baseUrl: string;
149
157
  /** OpenAPI JSON endpoint paths or absolute URLs to load */
150
158
  swaggerUrls?: string[];
159
+ /** Filter generated methods to specific operation IDs */
160
+ operationIds?: string[];
161
+ /** Filter generated methods to specific tag names */
162
+ tags?: string[];
151
163
  /** Username for Basic Auth */
152
164
  username?: string;
153
165
  /** Password for Basic Auth */
154
166
  password?: string;
155
167
  /** Bearer token for OAuth2 */
156
168
  oauthToken?: string;
169
+ /** Raw CSRF token — sets x-csrf-token header on every request */
170
+ authToken?: string;
157
171
  /** Request timeout in ms (default 30000) */
158
172
  timeout?: number;
159
173
  /** Number of retry attempts on transient failures (default 2) */
@@ -164,8 +178,10 @@ export interface LiferayClientOptions {
164
178
 
165
179
  /**
166
180
  * Main Liferay Headless API client.
167
- * Service namespaces (e.g. `client.headlessDelivery`) are dynamically populated
168
- * after `init()` is called or lazily on first access when `autoGenerate=true`.
181
+ *
182
+ * After `init()`, services are accessible as `client.<namespace>.<tag>.<method>()`.
183
+ * The namespace is derived from the OpenAPI `info.title` (camelCased);
184
+ * tags become sub-namespaces within it.
169
185
  */
170
186
  export declare class LiferayHeadlessClient {
171
187
  constructor(options: LiferayClientOptions);
@@ -201,12 +217,15 @@ export declare class LiferayHeadlessClient {
201
217
  /** Clear all cached Swagger schemas and reset generated services. */
202
218
  clearSchemaCache(): void;
203
219
 
204
- /** Returns all available service namespace names (derived from tags). */
220
+ /** Returns all available top-level service namespace names. */
205
221
  getServiceNames(): string[];
206
222
 
207
223
  /** Returns all method names for a given service namespace. */
208
224
  getMethodNames(serviceName: string): string[];
209
225
 
210
- /** Dynamically accessible service namespaces populated at runtime */
211
- [service: string]: Record<string, ApiMethod> | unknown;
226
+ /**
227
+ * Dynamically accessible service namespaces populated at runtime.
228
+ * Structure: client[namespace][tag][method]
229
+ */
230
+ [namespace: string]: Record<string, Record<string, ApiMethod>> | unknown;
212
231
  }