@appweaver/client 1.0.22 → 1.0.24

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.
Files changed (117) hide show
  1. package/cjs/angular/index.d.ts +1 -0
  2. package/cjs/angular/index.js +17 -0
  3. package/{clients → cjs/clients}/angular-client.d.ts +7 -1
  4. package/{clients → cjs/clients}/base-client-interface.d.ts +18 -3
  5. package/{clients → cjs/clients}/index.d.ts +0 -1
  6. package/{clients → cjs/clients}/index.js +0 -1
  7. package/{generators → cjs/generators}/generate-client.js +2 -1
  8. package/cjs/package.json +3 -0
  9. package/{weaver-client.js → cjs/weaver-client.js} +1 -1
  10. package/esm/angular/index.d.ts +1 -0
  11. package/esm/angular/index.js +1 -0
  12. package/esm/clients/angular-client.d.ts +26 -0
  13. package/esm/clients/angular-client.js +53 -0
  14. package/esm/clients/base-client-interface.d.ts +86 -0
  15. package/esm/clients/base-client-interface.js +1 -0
  16. package/esm/clients/base-client.d.ts +216 -0
  17. package/esm/clients/base-client.js +242 -0
  18. package/esm/clients/fetch-client.d.ts +6 -0
  19. package/esm/clients/fetch-client.js +6 -0
  20. package/esm/clients/index.d.ts +4 -0
  21. package/esm/clients/index.js +4 -0
  22. package/esm/clients/modules/account-client.d.ts +69 -0
  23. package/esm/clients/modules/account-client.js +107 -0
  24. package/esm/clients/modules/auth-client.d.ts +56 -0
  25. package/esm/clients/modules/auth-client.js +74 -0
  26. package/esm/clients/modules/base-module.d.ts +17 -0
  27. package/esm/clients/modules/base-module.js +49 -0
  28. package/esm/clients/modules/files-client.d.ts +27 -0
  29. package/esm/clients/modules/files-client.js +42 -0
  30. package/esm/clients/modules/health-client.d.ts +25 -0
  31. package/esm/clients/modules/health-client.js +30 -0
  32. package/esm/clients/modules/index.d.ts +6 -0
  33. package/esm/clients/modules/index.js +6 -0
  34. package/esm/clients/modules/resource-client.d.ts +90 -0
  35. package/esm/clients/modules/resource-client.js +173 -0
  36. package/esm/clients/responses/file-data-response.d.ts +78 -0
  37. package/esm/clients/responses/file-data-response.js +105 -0
  38. package/esm/clients/responses/index.d.ts +1 -0
  39. package/esm/clients/responses/index.js +1 -0
  40. package/esm/commands/generate-command.d.ts +3 -0
  41. package/esm/commands/generate-command.js +122 -0
  42. package/esm/commands/index.d.ts +1 -0
  43. package/esm/commands/index.js +1 -0
  44. package/esm/constants.d.ts +67 -0
  45. package/esm/constants.js +94 -0
  46. package/esm/errors/client-error.d.ts +6 -0
  47. package/esm/errors/client-error.js +11 -0
  48. package/esm/errors/index.d.ts +1 -0
  49. package/esm/errors/index.js +1 -0
  50. package/esm/generators/generate-client.d.ts +16 -0
  51. package/esm/generators/generate-client.js +399 -0
  52. package/esm/generators/generate-types.d.ts +9 -0
  53. package/esm/generators/generate-types.js +486 -0
  54. package/esm/generators/index.d.ts +2 -0
  55. package/esm/generators/index.js +2 -0
  56. package/esm/index.d.ts +2 -0
  57. package/esm/index.js +2 -0
  58. package/esm/package.json +3 -0
  59. package/esm/types/index.d.ts +1 -0
  60. package/esm/types/index.js +1 -0
  61. package/esm/types/routes.d.ts +15 -0
  62. package/esm/types/routes.js +1 -0
  63. package/esm/utils/index.d.ts +1 -0
  64. package/esm/utils/index.js +1 -0
  65. package/esm/utils/schema-util.d.ts +3 -0
  66. package/esm/utils/schema-util.js +39 -0
  67. package/package.json +39 -7
  68. /package/{clients → cjs/clients}/angular-client.js +0 -0
  69. /package/{clients → cjs/clients}/base-client-interface.js +0 -0
  70. /package/{clients → cjs/clients}/base-client.d.ts +0 -0
  71. /package/{clients → cjs/clients}/base-client.js +0 -0
  72. /package/{clients → cjs/clients}/fetch-client.d.ts +0 -0
  73. /package/{clients → cjs/clients}/fetch-client.js +0 -0
  74. /package/{clients → cjs/clients}/modules/account-client.d.ts +0 -0
  75. /package/{clients → cjs/clients}/modules/account-client.js +0 -0
  76. /package/{clients → cjs/clients}/modules/auth-client.d.ts +0 -0
  77. /package/{clients → cjs/clients}/modules/auth-client.js +0 -0
  78. /package/{clients → cjs/clients}/modules/base-module.d.ts +0 -0
  79. /package/{clients → cjs/clients}/modules/base-module.js +0 -0
  80. /package/{clients → cjs/clients}/modules/files-client.d.ts +0 -0
  81. /package/{clients → cjs/clients}/modules/files-client.js +0 -0
  82. /package/{clients → cjs/clients}/modules/health-client.d.ts +0 -0
  83. /package/{clients → cjs/clients}/modules/health-client.js +0 -0
  84. /package/{clients → cjs/clients}/modules/index.d.ts +0 -0
  85. /package/{clients → cjs/clients}/modules/index.js +0 -0
  86. /package/{clients → cjs/clients}/modules/resource-client.d.ts +0 -0
  87. /package/{clients → cjs/clients}/modules/resource-client.js +0 -0
  88. /package/{clients → cjs/clients}/responses/file-data-response.d.ts +0 -0
  89. /package/{clients → cjs/clients}/responses/file-data-response.js +0 -0
  90. /package/{clients → cjs/clients}/responses/index.d.ts +0 -0
  91. /package/{clients → cjs/clients}/responses/index.js +0 -0
  92. /package/{commands → cjs/commands}/generate-command.d.ts +0 -0
  93. /package/{commands → cjs/commands}/generate-command.js +0 -0
  94. /package/{commands → cjs/commands}/index.d.ts +0 -0
  95. /package/{commands → cjs/commands}/index.js +0 -0
  96. /package/{constants.d.ts → cjs/constants.d.ts} +0 -0
  97. /package/{constants.js → cjs/constants.js} +0 -0
  98. /package/{errors → cjs/errors}/client-error.d.ts +0 -0
  99. /package/{errors → cjs/errors}/client-error.js +0 -0
  100. /package/{errors → cjs/errors}/index.d.ts +0 -0
  101. /package/{errors → cjs/errors}/index.js +0 -0
  102. /package/{generators → cjs/generators}/generate-client.d.ts +0 -0
  103. /package/{generators → cjs/generators}/generate-types.d.ts +0 -0
  104. /package/{generators → cjs/generators}/generate-types.js +0 -0
  105. /package/{generators → cjs/generators}/index.d.ts +0 -0
  106. /package/{generators → cjs/generators}/index.js +0 -0
  107. /package/{index.d.ts → cjs/index.d.ts} +0 -0
  108. /package/{index.js → cjs/index.js} +0 -0
  109. /package/{types → cjs/types}/index.d.ts +0 -0
  110. /package/{types → cjs/types}/index.js +0 -0
  111. /package/{types → cjs/types}/routes.d.ts +0 -0
  112. /package/{types → cjs/types}/routes.js +0 -0
  113. /package/{utils → cjs/utils}/index.d.ts +0 -0
  114. /package/{utils → cjs/utils}/index.js +0 -0
  115. /package/{utils → cjs/utils}/schema-util.d.ts +0 -0
  116. /package/{utils → cjs/utils}/schema-util.js +0 -0
  117. /package/{weaver-client.d.ts → cjs/weaver-client.d.ts} +0 -0
@@ -0,0 +1,486 @@
1
+ import openapiTS, { astToString } from 'openapi-typescript';
2
+ import ts from 'typescript';
3
+ import { toSchemaObject } from '../utils/index.js';
4
+ import { ACCOUNT_MODULE_TYPE, ACCOUNT_TYPES, AUTH_MODULE_TYPE, AUTH_TYPES, CONFIG_FIELD, HEALTH_MODULE_TYPE, HEALTH_TYPES, RESOURCE_MODULE_TYPE, RESOURCE_TYPES } from '../constants.js';
5
+ /**
6
+ * Generates TypeScript types based on an OpenAPI V3 schema content.
7
+ *
8
+ * @param {string | OpenAPI3} schema - The OpenAPI V3 schema to generate types from. The value can be a string
9
+ * representing JSON or YAML format, or already parsed OpenAPI3 object.
10
+ * @return {Promise<string>} A promise that resolves to a string containing the generated TypeScript types.
11
+ */
12
+ export async function generateTypes(schema) {
13
+ const schemaObject = typeof schema === 'string' ? await toSchemaObject(schema) : schema;
14
+ const ast = await openapiTS(schemaObject, {
15
+ exportType: true,
16
+ emptyObjectsUnknown: true,
17
+ enum: true,
18
+ enumValues: true,
19
+ transformProperty: (property, schemaObject) => {
20
+ const validationTags = [];
21
+ // Add validation JSDoc tags based on schema constraints
22
+ if (schemaObject['minLength'] !== undefined) {
23
+ validationTags.push(`@minLength ${schemaObject['minLength']}`);
24
+ }
25
+ if (schemaObject['maxLength'] !== undefined) {
26
+ validationTags.push(`@maxLength ${schemaObject['maxLength']}`);
27
+ }
28
+ if (schemaObject['minimum'] !== undefined) {
29
+ validationTags.push(`@minimum ${schemaObject['minimum']}`);
30
+ }
31
+ if (schemaObject['maximum'] !== undefined) {
32
+ validationTags.push(`@maximum ${schemaObject['maximum']}`);
33
+ }
34
+ if (schemaObject['pattern'] !== undefined) {
35
+ validationTags.push(`@pattern ${schemaObject['pattern']}`);
36
+ }
37
+ if (schemaObject['format'] !== undefined) {
38
+ validationTags.push(`@format ${schemaObject['format']}`);
39
+ }
40
+ if (validationTags.length > 0) {
41
+ const newProperty = ts.factory.updatePropertySignature(property, property.modifiers, property.name, property.questionToken, property.type);
42
+ const jsDocText = `*\n * ${validationTags.join('\n * ')}\n `;
43
+ ts.addSyntheticLeadingComment(newProperty, ts.SyntaxKind.MultiLineCommentTrivia, jsDocText, true);
44
+ return newProperty;
45
+ }
46
+ return property;
47
+ }
48
+ });
49
+ let typesContent = astToString(deduplicateUnionConstituents(ast));
50
+ typesContent = extractSchemaTypes(typesContent);
51
+ typesContent = combineModuleTypes(typesContent, schemaObject);
52
+ typesContent = deduplicateExportedTypes(typesContent);
53
+ return replaceFileUploadTypes(typesContent);
54
+ }
55
+ /**
56
+ * Extracts inline schema types from the generated `schemas` block.
57
+ *
58
+ * Finds entries like:
59
+ * /** SomeTypeName *\/
60
+ * "def-N": { ... };
61
+ *
62
+ * Lifts each one into:
63
+ * export type SomeTypeName = { ... };
64
+ *
65
+ * And replaces the inline body in `schemas` with a reference to the new type.
66
+ *
67
+ * @param {string} typeContent - The generated TypeScript type content as a string.
68
+ * @return {string} The transformed types content with extracted schema types.
69
+ */
70
+ function extractSchemaTypes(typeContent) {
71
+ // Matches the header: /** TypeName */ "def-N": <cursor here before the opening brace>
72
+ const extractedTypes = [];
73
+ const typeNames = new Set();
74
+ const entries = [];
75
+ // Maps "def-N" key → extracted type name, used to replace cross-references
76
+ const defToTypeName = new Map();
77
+ // Replace Record<...> types with object types so schema type can be extracted
78
+ const normalizedTypes = typeContent.replace(/(\/\*\*\s*\w+\s*\*\/\s*"def-\d+"):\s*Record<(\w+), (\w+)>;/g, `$1: { [key: $2]: $3 };`);
79
+ let m;
80
+ // Also capture the "def-N" key from the header
81
+ const headerPatternWithKey = /\/\*\*\s*(\w+)\s*\*\/\s*("def-\d+"):\s*(?=\{)/g;
82
+ while ((m = headerPatternWithKey.exec(normalizedTypes)) !== null) {
83
+ const typeName = m[1];
84
+ const defKey = m[2]; // e.g. "def-89"
85
+ const headerEnd = m.index + m[0].length; // points at '{'
86
+ // Walk forward counting braces to find the matching closing '}'
87
+ let depth = 0;
88
+ let i = headerEnd;
89
+ for (; i < normalizedTypes.length; i++) {
90
+ if (normalizedTypes[i] === '{') {
91
+ depth++;
92
+ }
93
+ else if (normalizedTypes[i] === '}') {
94
+ depth--;
95
+ if (depth === 0) {
96
+ break;
97
+ }
98
+ }
99
+ }
100
+ // Variable i is now the index of the closing '}'
101
+ // The entry ends after '}' followed by ';'
102
+ const bodyEnd = i; // index of closing '}'
103
+ const entryEnd = normalizedTypes[bodyEnd + 1] === ';' ? bodyEnd + 2 : bodyEnd + 1;
104
+ defToTypeName.set(defKey, typeName);
105
+ entries.push({
106
+ start: m.index,
107
+ headerEnd,
108
+ typeName,
109
+ body: normalizedTypes.slice(headerEnd, bodyEnd + 1),
110
+ end: entryEnd
111
+ });
112
+ }
113
+ if (entries.length === 0) {
114
+ return normalizedTypes;
115
+ }
116
+ // Build updated content by replacing each entry's body with the type name reference
117
+ let updatedBaseContent = '';
118
+ let cursor = 0;
119
+ for (const entry of entries) {
120
+ // Append everything up to the opening brace of the body
121
+ updatedBaseContent += normalizedTypes.slice(cursor, entry.headerEnd);
122
+ // Replace body with the extracted type name
123
+ updatedBaseContent += entry.typeName + ';';
124
+ cursor = entry.end;
125
+ // Collect the exported type (deduplicate)
126
+ if (!typeNames.has(entry.typeName)) {
127
+ typeNames.add(entry.typeName);
128
+ extractedTypes.push(`export type ${entry.typeName} = ${entry.body}\n`);
129
+ }
130
+ }
131
+ updatedBaseContent += normalizedTypes.slice(cursor);
132
+ const enumDefPrefixType = {};
133
+ // Replace all cross-references like components["schemas"]["def-89"] with the type name
134
+ // Also, extract all enum prefixes in a list
135
+ for (const [defKey, typeName] of defToTypeName) {
136
+ // The defKey already includes surrounding quotes, e.g. "def-89"
137
+ const refPattern = new RegExp(`components\\["schemas"]\\[${defKey}\\]`, 'g');
138
+ updatedBaseContent = updatedBaseContent.replace(refPattern, typeName);
139
+ extractedTypes.forEach((t, idx) => {
140
+ extractedTypes[idx] = t.replace(refPattern, typeName);
141
+ });
142
+ // Extract defKey prefix for enums to be replaced them with type name
143
+ const defKeyPrefix = defKey
144
+ .replace(/"/g, '')
145
+ .replace(/(^|-)([a-z])/g, (_, __, ch) => ch.toUpperCase())
146
+ .replace(/-(\d+)/g, '$1');
147
+ enumDefPrefixType[defKeyPrefix] = typeName;
148
+ }
149
+ const getFirstNumber = (value) => {
150
+ const match = value.match(/\d+/);
151
+ return match ? Number(match[0]) : -Infinity;
152
+ };
153
+ // Sort keys descending by definition ID number if present. This is required
154
+ // to prevent replacing the prefix 'Def125' with 'Def12' or 'Def1' sub-prefix.
155
+ const sortedEnumKeys = Object.keys(enumDefPrefixType).sort((a, b) => getFirstNumber(b) - getFirstNumber(a));
156
+ // Replace enum definition prefixes with corresponding type names
157
+ for (const prefix of sortedEnumKeys) {
158
+ const typeName = enumDefPrefixType[prefix];
159
+ const exportEnumPattern = new RegExp(`export enum ${prefix}(.*){`, 'g');
160
+ updatedBaseContent = updatedBaseContent.replace(exportEnumPattern, `export enum ${typeName}$1{`);
161
+ const referenceEnumPattern = new RegExp(`: ${prefix}(.*);`, 'g');
162
+ extractedTypes.forEach((t, idx) => {
163
+ extractedTypes[idx] = t.replace(referenceEnumPattern, `: ${typeName}$1;`);
164
+ });
165
+ }
166
+ // Remove redundant "Format: ..." prose lines since the @format tag already captures this.
167
+ // If the line ends with */ (end-of-comment), preserve the closing; otherwise remove entirely.
168
+ const redundantFormatRegex = /\n(\s*)\* Format: [^\n]*/g;
169
+ const stripFormat = (s) => s.replace(redundantFormatRegex, (m, g) => m.endsWith('*/') ? `\n${g}*/` : '');
170
+ // Merge consecutive multiline comment endings and openings: */ \n /** → *
171
+ const doubleCommentsRegex = /(\s*)\*\/\s*\n\s*\/\*\*/g;
172
+ updatedBaseContent = stripFormat(updatedBaseContent.replace(doubleCommentsRegex, '$1*'));
173
+ const joined = extractedTypes.join('\n');
174
+ const mergedExtractedTypes = stripFormat(joined.replace(doubleCommentsRegex, '$1*'));
175
+ return updatedBaseContent + '\n' + mergedExtractedTypes;
176
+ }
177
+ /**
178
+ * Deduplicates and simplifies union type constituents in the provided TypeScript Abstract Syntax Tree (AST).
179
+ * Ensures that duplicate constituents are removed and shared types across parenthesized union branches are hoisted.
180
+ *
181
+ * @param {ts.Node[]} ast - An array of TypeScript nodes representing the AST to process and deduplicate.
182
+ * @return {ts.Node[]} An array of transformed TypeScript nodes with union type constituents deduplicated and simplified.
183
+ */
184
+ function deduplicateUnionConstituents(ast) {
185
+ const printer = ts.createPrinter();
186
+ const dummyFile = ts.createSourceFile('_.ts', '', ts.ScriptTarget.Latest);
187
+ const printType = (node) => printer.printNode(ts.EmitHint.Unspecified, node, dummyFile);
188
+ const transformer = (context) => {
189
+ const visit = (node) => {
190
+ // Bottom-up: fix nested unions before outer ones
191
+ node = ts.visitEachChild(node, visit, context);
192
+ if (!ts.isUnionTypeNode(node))
193
+ return node;
194
+ const types = Array.from(node.types);
195
+ // Deduplicate direct top-level members
196
+ const seenDirect = new Set();
197
+ const deduped = [];
198
+ for (const t of types) {
199
+ const key = printType(t);
200
+ if (!seenDirect.has(key)) {
201
+ seenDirect.add(key);
202
+ deduped.push(t);
203
+ }
204
+ }
205
+ // Hoist types shared across at least 2 parenthesized union branches
206
+ const parenBranches = deduped
207
+ .map((t, i) => ({ i, t }))
208
+ .filter(({ t }) => ts.isParenthesizedTypeNode(t) && ts.isUnionTypeNode(t.type));
209
+ if (parenBranches.length < 2) {
210
+ // Nothing to hoist, still emit step-1 deduplication result if it changed
211
+ if (deduped.length === types.length)
212
+ return node;
213
+ return ts.factory.createUnionTypeNode(deduped);
214
+ }
215
+ // Count how many branches each inner type key appears in
216
+ const countInBranches = new Map();
217
+ for (const { t } of parenBranches) {
218
+ const innerUnion = t
219
+ .type;
220
+ const seenInBranch = new Set();
221
+ for (const inner of innerUnion.types) {
222
+ const key = printType(inner);
223
+ if (!seenInBranch.has(key)) {
224
+ seenInBranch.add(key);
225
+ if (countInBranches.has(key)) {
226
+ countInBranches.get(key).count++;
227
+ }
228
+ else {
229
+ countInBranches.set(key, { count: 1, node: inner });
230
+ }
231
+ }
232
+ }
233
+ }
234
+ // Types that appear in at least 2 branches, or already exist directly in the outer union
235
+ const toHoist = new Map();
236
+ for (const [key, { count, node: n }] of countInBranches) {
237
+ if (count >= 2 || seenDirect.has(key)) {
238
+ toHoist.set(key, n);
239
+ }
240
+ }
241
+ if (toHoist.size === 0) {
242
+ if (deduped.length === types.length)
243
+ return node;
244
+ return ts.factory.createUnionTypeNode(deduped);
245
+ }
246
+ // Rebuild the union
247
+ const result = [];
248
+ const hoistedKeys = new Set(toHoist.keys());
249
+ for (const t of deduped) {
250
+ if (ts.isParenthesizedTypeNode(t) && ts.isUnionTypeNode(t.type)) {
251
+ const innerUnion = t.type;
252
+ const remaining = innerUnion.types.filter((inner) => !hoistedKeys.has(printType(inner)));
253
+ if (remaining.length === 0) {
254
+ // Branch fully absorbed — discard
255
+ }
256
+ else if (remaining.length === 1) {
257
+ result.push(remaining[0]); // unwrap
258
+ }
259
+ else {
260
+ result.push(ts.factory.createParenthesizedType(ts.factory.createUnionTypeNode(remaining)));
261
+ }
262
+ }
263
+ else {
264
+ result.push(t);
265
+ }
266
+ }
267
+ // Append each hoisted type once (skip if already present directly)
268
+ for (const [key, hoistedNode] of toHoist) {
269
+ if (!seenDirect.has(key)) {
270
+ result.push(hoistedNode);
271
+ }
272
+ }
273
+ return ts.factory.createUnionTypeNode(result);
274
+ };
275
+ return (root) => ts.visitNode(root, visit);
276
+ };
277
+ return ast.map((node) => ts.transform(node, [transformer]).transformed[0]);
278
+ }
279
+ /**
280
+ * Combines various module types into a single schema definition by analyzing the provided type content.
281
+ * The combined types are then appended to the original type content and used by the client to allow type
282
+ * inference for client modules (e.g., AuthClient<AuthModuleType>, ResourceClient<UserResourceModuleType>, etc.)
283
+ *
284
+ * @param {string} typeContent The TypeScript content containing type definitions to be processed.
285
+ * @param {OpenAPI3} schema The OpenAPI v3 schema object.
286
+ * @return {string} A string representing the modified type content with additional module type definitions.
287
+ */
288
+ function combineModuleTypes(typeContent, schema) {
289
+ // Map of resource names with corresponding used types
290
+ // (e.g., User -> { single: UserSingle, ...})
291
+ const resourceTypes = {};
292
+ // Map of used auth types in generated schema
293
+ // (e.g., { loginRequest: LoginRequest })
294
+ const authTypes = {};
295
+ // Map of used auth types in generated schema
296
+ // (e.g., { statusResponse: AccountStatusResponse })
297
+ const accountTypes = {};
298
+ // Map of used health types in generated schema
299
+ // (e.g., { checkResponse: HealthCheckResponse })
300
+ const healthTypes = {};
301
+ // Utility function for capitalizing type names
302
+ const capitalize = (text) => text.charAt(0).toUpperCase() + text.slice(1);
303
+ // Utility function for collecting used types
304
+ const collectUsedTypes = (sourceTypes, targetObj, getTypeName, typesContent) => {
305
+ for (const type of sourceTypes) {
306
+ const typeName = getTypeName(type);
307
+ const resourcePattern = new RegExp(`export type ${typeName} = {`, 'g');
308
+ if (resourcePattern.test(typesContent)) {
309
+ targetObj[type] = typeName;
310
+ }
311
+ else {
312
+ targetObj[type] = 'never';
313
+ }
314
+ }
315
+ };
316
+ // Utility function for building TypeScript type from used values
317
+ const buildModuleType = (name, types) => {
318
+ const typeValues = Object.entries(types).map(([key, value]) => `${key}: ${value}`);
319
+ return `export type ${name} = { ${typeValues.join(';')} };`;
320
+ };
321
+ // Find all resource types with used schema types
322
+ for (const type of RESOURCE_TYPES) {
323
+ const typeSuffix = capitalize(type);
324
+ const resourcePattern = new RegExp(`export type (.+)${typeSuffix} = {`, 'g');
325
+ let match;
326
+ while ((match = resourcePattern.exec(typeContent)) !== null) {
327
+ const resourceName = match[1];
328
+ resourceTypes[resourceName] ??= {};
329
+ resourceTypes[resourceName][type] = `${resourceName}${typeSuffix}`;
330
+ }
331
+ }
332
+ // Resource types that are not found in schema are set to 'never' type to
333
+ // prevent their usage by generated client
334
+ for (const type of RESOURCE_TYPES) {
335
+ for (const resourceType of Object.values(resourceTypes)) {
336
+ if (!resourceType[type]) {
337
+ resourceType[type] = 'never';
338
+ }
339
+ }
340
+ }
341
+ // Find all used auth types
342
+ collectUsedTypes(AUTH_TYPES, authTypes, (type) => capitalize(type), typeContent);
343
+ // Extract identity type from authentication '/me' success response
344
+ const config = schema[CONFIG_FIELD];
345
+ authTypes['identity'] =
346
+ extractResponseType(typeContent, `${config?.routePrefixes?.auth ?? '/auth'}/me`, 'get') ?? 'never';
347
+ // Find all used account types
348
+ collectUsedTypes(ACCOUNT_TYPES, accountTypes, (type) => `Account${capitalize(type)}`, typeContent);
349
+ // Find all used health types
350
+ collectUsedTypes(HEALTH_TYPES, healthTypes, (type) => `Health${capitalize(type)}`, typeContent);
351
+ // Combine all module types into a single string and append to the type content
352
+ const moduleTypes = [
353
+ buildModuleType(AUTH_MODULE_TYPE, authTypes),
354
+ buildModuleType(ACCOUNT_MODULE_TYPE, accountTypes),
355
+ buildModuleType(HEALTH_MODULE_TYPE, healthTypes),
356
+ ...Object.entries(resourceTypes).map(([name, types]) => buildModuleType(`${name}${RESOURCE_MODULE_TYPE}`, types))
357
+ ];
358
+ return typeContent + `\n${moduleTypes.join('\n\n')}`;
359
+ }
360
+ /**
361
+ * Deduplicates exported type and enum names in the provided TypeScript content.
362
+ * Ensures no naming conflicts between types and enums with the same name and resolves duplicate type names.
363
+ *
364
+ * @param {string} content The string content of the TypeScript file to process.
365
+ * @return {string} A modified string with deduplicated names for exported types and enums.
366
+ */
367
+ function deduplicateExportedTypes(content) {
368
+ const escapeRegExp = (s) => {
369
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
370
+ };
371
+ // Collect type names (detect duplicates) and enum names
372
+ const typePattern = /\bexport type (\w+)/g;
373
+ const enumPattern = /\bexport enum (\w+)\s*\{/g;
374
+ const seenTypes = new Set();
375
+ const duplicateTypes = new Set();
376
+ const enumNames = new Set();
377
+ let m;
378
+ while ((m = typePattern.exec(content)) !== null) {
379
+ const name = m[1];
380
+ if (seenTypes.has(name)) {
381
+ duplicateTypes.add(name);
382
+ }
383
+ else {
384
+ seenTypes.add(name);
385
+ }
386
+ }
387
+ while ((m = enumPattern.exec(content)) !== null) {
388
+ enumNames.add(m[1]);
389
+ }
390
+ // Handle type-enum collisions
391
+ const typeEnumCollisions = [...seenTypes].filter((n) => enumNames.has(n));
392
+ for (const name of typeEnumCollisions) {
393
+ const escaped = escapeRegExp(name);
394
+ const placeholder = `__DEDUP_${name}__`;
395
+ // Protect enum declaration from the global replacement
396
+ content = content.replace(new RegExp(`\\bexport enum ${escaped}\\s*\\{`, 'g'), `export enum ${placeholder} {`);
397
+ // Rename all type-position occurrences (declaration + references) → NameType
398
+ content = content.replace(new RegExp(`\\b${escaped}\\b`, 'g'), `${name}Type`);
399
+ // Finalize enum name → NameEnum
400
+ content = content.replace(new RegExp(`\\bexport enum ${escapeRegExp(placeholder)}\\s*\\{`, 'g'), `export enum ${name}Enum {`);
401
+ }
402
+ // Handle duplicate type names (rename second occurrence only)
403
+ for (const name of duplicateTypes) {
404
+ if (typeEnumCollisions.includes(name)) {
405
+ // already handled above
406
+ continue;
407
+ }
408
+ const escaped = escapeRegExp(name);
409
+ let firstSeen = false;
410
+ content = content.replace(new RegExp(`\\bexport type ${escaped}\\b`, 'g'), (match) => {
411
+ if (!firstSeen) {
412
+ firstSeen = true;
413
+ return match;
414
+ }
415
+ return `export type ${name}Type`;
416
+ });
417
+ }
418
+ return content;
419
+ }
420
+ /**
421
+ * Replaces the `string` and `string[]` types with `File` and `File[]` respectively
422
+ * in TypeScript type definitions related to file uploads, only if the type body
423
+ * includes the annotation `@format binary`.
424
+ *
425
+ * This function processes a TypeScript file content string, identifies type definitions
426
+ * with names matching the pattern `export type <name>FileUpload = {...}`, and updates their
427
+ * definitions to reflect file-like objects instead of plain strings for better type safety.
428
+ *
429
+ * @param {string} typeContent - The TypeScript file content as a string, which
430
+ * contains the type definitions to be inspected and replaced.
431
+ * @return {string} The updated TypeScript file content, with the modified type
432
+ * definitions that reflect `File` and `File[]` for annotated file upload types.
433
+ */
434
+ function replaceFileUploadTypes(typeContent) {
435
+ const fileUploadPattern = /export type \w+FileUpload = \{/g;
436
+ let result = typeContent;
437
+ let offset = 0;
438
+ let match;
439
+ while ((match = fileUploadPattern.exec(typeContent)) !== null) {
440
+ const bodyStart = match.index + match[0].length - 1; // index of opening '{'
441
+ // Extract body using brace-depth counting
442
+ let depth = 0;
443
+ let i = bodyStart;
444
+ for (; i < typeContent.length; i++) {
445
+ if (typeContent[i] === '{')
446
+ depth++;
447
+ else if (typeContent[i] === '}') {
448
+ depth--;
449
+ if (depth === 0)
450
+ break;
451
+ }
452
+ }
453
+ const body = typeContent.slice(bodyStart, i + 1);
454
+ // Guard: only transform if the body contains @format binary
455
+ if (!body.includes('@format binary')) {
456
+ continue;
457
+ }
458
+ // Replace string[] before string to avoid double-replacement
459
+ const newBody = body
460
+ .replace(/\bstring\[]/g, 'File[]')
461
+ .replace(/\bstring\b/g, 'File');
462
+ // Splice the replacement into the result, accounting for prior replacements
463
+ const absoluteStart = match.index + match[0].length - 1 + offset;
464
+ const absoluteEnd = absoluteStart + body.length;
465
+ result =
466
+ result.slice(0, absoluteStart) + newBody + result.slice(absoluteEnd);
467
+ offset += newBody.length - body.length;
468
+ }
469
+ return result;
470
+ }
471
+ /**
472
+ * Extracts the response type from a provided string of types based on the specified path, method, and status code.
473
+ *
474
+ * @param {string} types - The string containing type definitions.
475
+ * @param {string} path - The full API path to match in the type definitions.
476
+ * @param {string} method - The HTTP method to look for (e.g., get, post, put, delete).
477
+ * @param {string} [code=200] - The HTTP response status code to match.
478
+ * @param {string} [mimeType='application/json'] - The HTTP response mime type to match.
479
+ * @return {string | undefined} The extracted response type if a match is found, otherwise undefined.
480
+ */
481
+ function extractResponseType(types, path, method, code = '200', mimeType = 'application/json') {
482
+ const escapedPath = path.replace(/[/[\]{}.*+?^$|()\\]/g, '\\$&');
483
+ const escapedMimeType = mimeType.replace(/[/[\]{}.*+?^$|()\\]/g, '\\$&');
484
+ const regex = new RegExp(`"${escapedPath}":\\s*\\{.*?${method}:\\s*\\{.*?"?${code}"?:\\s*\\{.*?"${escapedMimeType}":\\s*(\\w+)`, 's');
485
+ return types.match(regex)?.[1];
486
+ }
@@ -0,0 +1,2 @@
1
+ export * from './generate-client.js';
2
+ export * from './generate-types.js';
@@ -0,0 +1,2 @@
1
+ export * from './generate-client.js';
2
+ export * from './generate-types.js';
package/esm/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './clients/index.js';
2
+ export * from './errors/index.js';
package/esm/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './clients/index.js';
2
+ export * from './errors/index.js';
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1 @@
1
+ export * from './routes.js';
@@ -0,0 +1 @@
1
+ export * from './routes.js';
@@ -0,0 +1,15 @@
1
+ export type RoutePathConfig = {
2
+ resourcePaths: {
3
+ name: string;
4
+ basePath: string;
5
+ }[];
6
+ routePrefixes: {
7
+ api: string;
8
+ static: string;
9
+ health: string;
10
+ auth: string;
11
+ account: string;
12
+ files: string;
13
+ };
14
+ };
15
+ export type RouteType = 'resource' | 'auth' | 'account' | 'health' | 'files' | 'custom';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export * from './schema-util.js';
@@ -0,0 +1 @@
1
+ export * from './schema-util.js';
@@ -0,0 +1,3 @@
1
+ import { OpenAPI3 } from 'openapi-typescript';
2
+ export declare function readSchemaContent(schemaPath: string): Promise<string>;
3
+ export declare function toSchemaObject(schemaContent: string): Promise<OpenAPI3>;
@@ -0,0 +1,39 @@
1
+ import fsp from 'node:fs/promises';
2
+ import yaml from 'js-yaml';
3
+ export async function readSchemaContent(schemaPath) {
4
+ let schemaUrl;
5
+ try {
6
+ schemaUrl = new URL(schemaPath);
7
+ }
8
+ catch {
9
+ return await fsp.readFile(schemaPath, 'utf8');
10
+ }
11
+ if (schemaUrl.protocol === 'http:' || schemaUrl.protocol === 'https:') {
12
+ let res;
13
+ try {
14
+ res = await fetch(schemaUrl.toString());
15
+ }
16
+ catch {
17
+ throw new Error(`Cannot access schema URL: ${schemaUrl}`);
18
+ }
19
+ if (!res.ok) {
20
+ throw new Error(`Fetching schema ${schemaUrl} failed: ${res.statusText}`);
21
+ }
22
+ return await res.text();
23
+ }
24
+ return await fsp.readFile(schemaUrl, 'utf8');
25
+ }
26
+ export async function toSchemaObject(schemaContent) {
27
+ try {
28
+ return JSON.parse(schemaContent);
29
+ }
30
+ catch {
31
+ // not JSON, try YAML
32
+ }
33
+ try {
34
+ return yaml.load(schemaContent);
35
+ }
36
+ catch {
37
+ throw Error('Unable to parse schema object in JSON or YAML format.');
38
+ }
39
+ }
package/package.json CHANGED
@@ -1,13 +1,37 @@
1
1
  {
2
2
  "name": "@appweaver/client",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "Appweaver - the backend framework for AI-first development (@client)",
5
5
  "author": "Luka Matosevic",
6
6
  "license": "MIT",
7
- "main": "./index.js",
8
- "types": "./index.d.ts",
7
+ "main": "./cjs/index.js",
8
+ "module": "./esm/index.js",
9
+ "types": "./cjs/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./esm/index.d.ts",
14
+ "default": "./esm/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./cjs/index.d.ts",
18
+ "default": "./cjs/index.js"
19
+ }
20
+ },
21
+ "./angular": {
22
+ "import": {
23
+ "types": "./esm/angular/index.d.ts",
24
+ "default": "./esm/angular/index.js"
25
+ },
26
+ "require": {
27
+ "types": "./cjs/angular/index.d.ts",
28
+ "default": "./cjs/angular/index.js"
29
+ }
30
+ },
31
+ "./package.json": "./package.json"
32
+ },
9
33
  "bin": {
10
- "weaver-client": "./weaver-client.js"
34
+ "weaver-client": "./cjs/weaver-client.js"
11
35
  },
12
36
  "repository": {
13
37
  "type": "git",
@@ -28,7 +52,7 @@
28
52
  "node": ">= 20"
29
53
  },
30
54
  "scripts": {
31
- "weaver-client": "node ./dist/weaver-client.js"
55
+ "weaver-client": "node ./dist/cjs/weaver-client.js"
32
56
  },
33
57
  "dependencies": {
34
58
  "commander": "15.0.0",
@@ -37,10 +61,18 @@
37
61
  "openapi-typescript": "7.13.0",
38
62
  "openapi-typescript-helpers": "0.1.0",
39
63
  "prettier": "3.8.4",
40
- "rxjs": "^7.8.0",
41
64
  "typescript": "5.9.3"
42
65
  },
43
66
  "devDependencies": {
44
- "@types/js-yaml": "4.0.9"
67
+ "@types/js-yaml": "4.0.9",
68
+ "rxjs": "^7.8.0"
69
+ },
70
+ "peerDependencies": {
71
+ "rxjs": "^7.8.0"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "rxjs": {
75
+ "optional": true
76
+ }
45
77
  }
46
78
  }
File without changes
File without changes
File without changes
File without changes
File without changes