strapi-typed-client 1.0.0

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 (176) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -0
  3. package/dist/cli/commands/check.d.ts +27 -0
  4. package/dist/cli/commands/check.d.ts.map +1 -0
  5. package/dist/cli/commands/check.js +91 -0
  6. package/dist/cli/commands/check.js.map +1 -0
  7. package/dist/cli/commands/generate.d.ts +28 -0
  8. package/dist/cli/commands/generate.d.ts.map +1 -0
  9. package/dist/cli/commands/generate.js +129 -0
  10. package/dist/cli/commands/generate.js.map +1 -0
  11. package/dist/cli/commands/watch.d.ts +21 -0
  12. package/dist/cli/commands/watch.d.ts.map +1 -0
  13. package/dist/cli/commands/watch.js +113 -0
  14. package/dist/cli/commands/watch.js.map +1 -0
  15. package/dist/cli/index.d.ts +14 -0
  16. package/dist/cli/index.d.ts.map +1 -0
  17. package/dist/cli/index.js +40 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/cli/utils/api-client.d.ts +41 -0
  20. package/dist/cli/utils/api-client.d.ts.map +1 -0
  21. package/dist/cli/utils/api-client.js +81 -0
  22. package/dist/cli/utils/api-client.js.map +1 -0
  23. package/dist/cli/utils/file-writer.d.ts +44 -0
  24. package/dist/cli/utils/file-writer.d.ts.map +1 -0
  25. package/dist/cli/utils/file-writer.js +89 -0
  26. package/dist/cli/utils/file-writer.js.map +1 -0
  27. package/dist/cli.d.ts +3 -0
  28. package/dist/cli.d.ts.map +1 -0
  29. package/dist/cli.js +37 -0
  30. package/dist/cli.js.map +1 -0
  31. package/dist/client.d.ts +622 -0
  32. package/dist/client.js +688 -0
  33. package/dist/core/endpoint-converter.d.ts +26 -0
  34. package/dist/core/endpoint-converter.d.ts.map +1 -0
  35. package/dist/core/endpoint-converter.js +161 -0
  36. package/dist/core/endpoint-converter.js.map +1 -0
  37. package/dist/core/generator/filters-generator.d.ts +22 -0
  38. package/dist/core/generator/filters-generator.d.ts.map +1 -0
  39. package/dist/core/generator/filters-generator.js +236 -0
  40. package/dist/core/generator/filters-generator.js.map +1 -0
  41. package/dist/core/index.d.ts +8 -0
  42. package/dist/core/index.d.ts.map +1 -0
  43. package/dist/core/index.js +11 -0
  44. package/dist/core/index.js.map +1 -0
  45. package/dist/core/schema-transformer.d.ts +19 -0
  46. package/dist/core/schema-transformer.d.ts.map +1 -0
  47. package/dist/core/schema-transformer.js +306 -0
  48. package/dist/core/schema-transformer.js.map +1 -0
  49. package/dist/generator/auth-api-generator.d.ts +14 -0
  50. package/dist/generator/auth-api-generator.d.ts.map +1 -0
  51. package/dist/generator/auth-api-generator.js +518 -0
  52. package/dist/generator/auth-api-generator.js.map +1 -0
  53. package/dist/generator/blocks-types-template.d.ts +134 -0
  54. package/dist/generator/blocks-types-template.d.ts.map +1 -0
  55. package/dist/generator/blocks-types-template.js +4 -0
  56. package/dist/generator/blocks-types-template.js.map +1 -0
  57. package/dist/generator/client-generator.d.ts +19 -0
  58. package/dist/generator/client-generator.d.ts.map +1 -0
  59. package/dist/generator/client-generator.js +707 -0
  60. package/dist/generator/client-generator.js.map +1 -0
  61. package/dist/generator/custom-api-generator.d.ts +26 -0
  62. package/dist/generator/custom-api-generator.d.ts.map +1 -0
  63. package/dist/generator/custom-api-generator.js +145 -0
  64. package/dist/generator/custom-api-generator.js.map +1 -0
  65. package/dist/generator/index-generator.d.ts +4 -0
  66. package/dist/generator/index-generator.d.ts.map +1 -0
  67. package/dist/generator/index-generator.js +11 -0
  68. package/dist/generator/index-generator.js.map +1 -0
  69. package/dist/generator/index.d.ts +14 -0
  70. package/dist/generator/index.d.ts.map +1 -0
  71. package/dist/generator/index.js +94 -0
  72. package/dist/generator/index.js.map +1 -0
  73. package/dist/generator/types-generator.d.ts +35 -0
  74. package/dist/generator/types-generator.d.ts.map +1 -0
  75. package/dist/generator/types-generator.js +641 -0
  76. package/dist/generator/types-generator.js.map +1 -0
  77. package/dist/index.d.ts +2 -0
  78. package/dist/index.js +4 -0
  79. package/dist/next/index.d.ts +16 -0
  80. package/dist/next/index.d.ts.map +1 -0
  81. package/dist/next/index.js +224 -0
  82. package/dist/next/index.js.map +1 -0
  83. package/dist/parser/custom-types-parser.d.ts +39 -0
  84. package/dist/parser/custom-types-parser.d.ts.map +1 -0
  85. package/dist/parser/custom-types-parser.js +206 -0
  86. package/dist/parser/custom-types-parser.js.map +1 -0
  87. package/dist/parser/index.d.ts +23 -0
  88. package/dist/parser/index.d.ts.map +1 -0
  89. package/dist/parser/index.js +457 -0
  90. package/dist/parser/index.js.map +1 -0
  91. package/dist/parser/routes-parser.d.ts +21 -0
  92. package/dist/parser/routes-parser.d.ts.map +1 -0
  93. package/dist/parser/routes-parser.js +184 -0
  94. package/dist/parser/routes-parser.js.map +1 -0
  95. package/dist/plugin/admin/src/index.d.ts +16 -0
  96. package/dist/plugin/admin/src/index.d.ts.map +1 -0
  97. package/dist/plugin/admin/src/index.js +30 -0
  98. package/dist/plugin/admin/src/index.js.map +1 -0
  99. package/dist/plugin/admin/src/pages/HomePage.d.ts +2 -0
  100. package/dist/plugin/admin/src/pages/HomePage.d.ts.map +1 -0
  101. package/dist/plugin/admin/src/pages/HomePage.js +273 -0
  102. package/dist/plugin/admin/src/pages/HomePage.js.map +1 -0
  103. package/dist/plugin/admin/src/pluginId.d.ts +2 -0
  104. package/dist/plugin/admin/src/pluginId.d.ts.map +1 -0
  105. package/dist/plugin/admin/src/pluginId.js +2 -0
  106. package/dist/plugin/admin/src/pluginId.js.map +1 -0
  107. package/dist/plugin/index.d.ts +22 -0
  108. package/dist/plugin/index.d.ts.map +1 -0
  109. package/dist/plugin/index.js +22 -0
  110. package/dist/plugin/index.js.map +1 -0
  111. package/dist/plugin/server/src/config/index.d.ts +31 -0
  112. package/dist/plugin/server/src/config/index.d.ts.map +1 -0
  113. package/dist/plugin/server/src/config/index.js +35 -0
  114. package/dist/plugin/server/src/config/index.js.map +1 -0
  115. package/dist/plugin/server/src/controllers/schema.d.ts +41 -0
  116. package/dist/plugin/server/src/controllers/schema.d.ts.map +1 -0
  117. package/dist/plugin/server/src/controllers/schema.js +51 -0
  118. package/dist/plugin/server/src/controllers/schema.js.map +1 -0
  119. package/dist/plugin/server/src/index.d.ts +114 -0
  120. package/dist/plugin/server/src/index.d.ts.map +1 -0
  121. package/dist/plugin/server/src/index.js +76 -0
  122. package/dist/plugin/server/src/index.js.map +1 -0
  123. package/dist/plugin/server/src/routes/index.d.ts +14 -0
  124. package/dist/plugin/server/src/routes/index.d.ts.map +1 -0
  125. package/dist/plugin/server/src/routes/index.js +23 -0
  126. package/dist/plugin/server/src/routes/index.js.map +1 -0
  127. package/dist/plugin/server/src/services/endpoints.d.ts +36 -0
  128. package/dist/plugin/server/src/services/endpoints.d.ts.map +1 -0
  129. package/dist/plugin/server/src/services/endpoints.js +523 -0
  130. package/dist/plugin/server/src/services/endpoints.js.map +1 -0
  131. package/dist/plugin/server/src/services/schema.d.ts +25 -0
  132. package/dist/plugin/server/src/services/schema.d.ts.map +1 -0
  133. package/dist/plugin/server/src/services/schema.js +164 -0
  134. package/dist/plugin/server/src/services/schema.js.map +1 -0
  135. package/dist/schema-meta.ts +7 -0
  136. package/dist/schema-types.d.ts +104 -0
  137. package/dist/schema-types.d.ts.map +1 -0
  138. package/dist/schema-types.js +3 -0
  139. package/dist/schema-types.js.map +1 -0
  140. package/dist/shared/constants.d.ts +99 -0
  141. package/dist/shared/constants.d.ts.map +1 -0
  142. package/dist/shared/constants.js +89 -0
  143. package/dist/shared/constants.js.map +1 -0
  144. package/dist/shared/endpoint-types.d.ts +42 -0
  145. package/dist/shared/endpoint-types.d.ts.map +1 -0
  146. package/dist/shared/endpoint-types.js +6 -0
  147. package/dist/shared/endpoint-types.js.map +1 -0
  148. package/dist/shared/index.d.ts +12 -0
  149. package/dist/shared/index.d.ts.map +1 -0
  150. package/dist/shared/index.js +13 -0
  151. package/dist/shared/index.js.map +1 -0
  152. package/dist/shared/naming-utils.d.ts +55 -0
  153. package/dist/shared/naming-utils.d.ts.map +1 -0
  154. package/dist/shared/naming-utils.js +106 -0
  155. package/dist/shared/naming-utils.js.map +1 -0
  156. package/dist/shared/schema-hash.d.ts +39 -0
  157. package/dist/shared/schema-hash.d.ts.map +1 -0
  158. package/dist/shared/schema-hash.js +67 -0
  159. package/dist/shared/schema-hash.js.map +1 -0
  160. package/dist/shared/strapi-schema-types.d.ts +77 -0
  161. package/dist/shared/strapi-schema-types.d.ts.map +1 -0
  162. package/dist/shared/strapi-schema-types.js +10 -0
  163. package/dist/shared/strapi-schema-types.js.map +1 -0
  164. package/dist/shared/string-utils.d.ts +68 -0
  165. package/dist/shared/string-utils.d.ts.map +1 -0
  166. package/dist/shared/string-utils.js +124 -0
  167. package/dist/shared/string-utils.js.map +1 -0
  168. package/dist/transformer/index.d.ts +17 -0
  169. package/dist/transformer/index.d.ts.map +1 -0
  170. package/dist/transformer/index.js +83 -0
  171. package/dist/transformer/index.js.map +1 -0
  172. package/dist/types.d.ts +3430 -0
  173. package/dist/types.js +3 -0
  174. package/package.json +152 -0
  175. package/strapi-admin.js +2 -0
  176. package/strapi-server.js +2 -0
@@ -0,0 +1,641 @@
1
+ import { TypeTransformer } from '../transformer/index.js';
2
+ import { generateFilterUtilityTypes, generateEntityFilters, generateTypedQueryParams, } from '../core/generator/filters-generator.js';
3
+ export class TypesGenerator {
4
+ transformer;
5
+ schema;
6
+ constructor() {
7
+ this.transformer = new TypeTransformer();
8
+ }
9
+ generate(schema) {
10
+ this.schema = schema;
11
+ const lines = [];
12
+ // Add header comment
13
+ lines.push('// Auto-generated TypeScript types from Strapi schema');
14
+ lines.push('// Do not edit manually');
15
+ lines.push('');
16
+ // Generate base types
17
+ lines.push(this.generateBaseTypes());
18
+ lines.push('');
19
+ // Generate helper types for populate params
20
+ lines.push('// Helper types for field and sort options in populate');
21
+ lines.push("type _EntityField<T> = Exclude<keyof T & string, '__typename'>");
22
+ lines.push('type _SortValue<T> = _EntityField<T> | `${_EntityField<T>}:${"asc" | "desc"}`');
23
+ lines.push('');
24
+ lines.push('// Apply fields narrowing from populate entry (e.g. populate: { item: { fields: ["title"] } })');
25
+ lines.push("type _ApplyFields<TFull, TBase, TEntry> = TEntry extends true ? TFull : TEntry extends { fields: readonly (infer F)[] } ? F extends string ? Pick<TBase, Extract<F | 'id' | 'documentId', keyof TBase>> & Omit<TFull, keyof TBase> : TFull : TFull");
26
+ lines.push('');
27
+ // Generate component types
28
+ for (const component of schema.components) {
29
+ lines.push(this.generateComponent(component));
30
+ lines.push('');
31
+ }
32
+ // Generate component Input types
33
+ for (const component of schema.components) {
34
+ lines.push(this.generateComponentInput(component));
35
+ lines.push('');
36
+ }
37
+ // Generate content type interfaces
38
+ for (const contentType of schema.contentTypes) {
39
+ lines.push(this.generateContentType(contentType));
40
+ lines.push('');
41
+ }
42
+ // Generate Input types for create/update operations
43
+ for (const contentType of schema.contentTypes) {
44
+ lines.push(this.generateInputType(contentType));
45
+ lines.push('');
46
+ }
47
+ // Generate PopulateParam types for type-safe populate
48
+ lines.push(this.generatePopulateParams(schema));
49
+ lines.push('');
50
+ // Generate Payload utility types
51
+ lines.push(this.generatePayloadUtilityTypes(schema));
52
+ lines.push('');
53
+ // Generate filter utility types
54
+ lines.push(generateFilterUtilityTypes());
55
+ lines.push('');
56
+ // Generate typed query params
57
+ lines.push(generateTypedQueryParams());
58
+ lines.push('');
59
+ // Generate entity-specific filters
60
+ for (const contentType of schema.contentTypes) {
61
+ lines.push(generateEntityFilters(contentType));
62
+ lines.push('');
63
+ }
64
+ return lines.join('\n');
65
+ }
66
+ generateBaseTypes() {
67
+ return `// Base types
68
+
69
+ export interface MediaFile {
70
+ id: number
71
+ name: string
72
+ alternativeText: string | null
73
+ caption: string | null
74
+ width: number | null
75
+ height: number | null
76
+ formats: unknown
77
+ hash: string
78
+ ext: string
79
+ mime: string
80
+ size: number
81
+ url: string
82
+ previewUrl: string | null
83
+ provider: string
84
+ createdAt: string
85
+ updatedAt: string
86
+ }
87
+
88
+ // Strapi Blocks Editor API Types
89
+ // Based on: https://docs.strapi.io/dev-docs/api/document/blocks
90
+
91
+ /**
92
+ * Main type for Strapi Blocks content
93
+ */
94
+ export type BlocksContent = Block[]
95
+
96
+ /**
97
+ * All possible block types
98
+ */
99
+ export type Block =
100
+ | ParagraphBlock
101
+ | HeadingBlock
102
+ | QuoteBlock
103
+ | CodeBlock
104
+ | ListBlock
105
+ | ImageBlock
106
+
107
+ /**
108
+ * Paragraph block - default text block
109
+ */
110
+ export interface ParagraphBlock {
111
+ type: 'paragraph'
112
+ children: InlineNode[]
113
+ }
114
+
115
+ /**
116
+ * Heading block - h1 to h6
117
+ */
118
+ export interface HeadingBlock {
119
+ type: 'heading'
120
+ level: 1 | 2 | 3 | 4 | 5 | 6
121
+ children: InlineNode[]
122
+ }
123
+
124
+ /**
125
+ * Quote block - blockquote
126
+ */
127
+ export interface QuoteBlock {
128
+ type: 'quote'
129
+ children: InlineNode[]
130
+ }
131
+
132
+ /**
133
+ * Code block - preformatted code with optional language
134
+ */
135
+ export interface CodeBlock {
136
+ type: 'code'
137
+ language?: string
138
+ children: InlineNode[]
139
+ }
140
+
141
+ /**
142
+ * List block - ordered or unordered
143
+ */
144
+ export interface ListBlock {
145
+ type: 'list'
146
+ format: 'ordered' | 'unordered'
147
+ children: ListItemBlock[]
148
+ }
149
+
150
+ /**
151
+ * List item - individual item in a list
152
+ */
153
+ export interface ListItemBlock {
154
+ type: 'list-item'
155
+ children: InlineNode[]
156
+ }
157
+
158
+ /**
159
+ * Image block - embedded image with optional caption
160
+ */
161
+ export interface ImageBlock {
162
+ type: 'image'
163
+ image: {
164
+ name: string
165
+ alternativeText?: string | null
166
+ url: string
167
+ caption?: string | null
168
+ width?: number
169
+ height?: number
170
+ formats?: unknown
171
+ hash: string
172
+ ext: string
173
+ mime: string
174
+ size: number
175
+ previewUrl?: string | null
176
+ provider: string
177
+ createdAt: string
178
+ updatedAt: string
179
+ }
180
+ children: InlineNode[]
181
+ }
182
+
183
+ /**
184
+ * Inline nodes - text formatting and inline elements
185
+ */
186
+ export type InlineNode = TextNode | LinkInline
187
+
188
+ /**
189
+ * Plain text node with optional formatting
190
+ */
191
+ export interface TextNode {
192
+ type: 'text'
193
+ text: string
194
+ bold?: boolean
195
+ italic?: boolean
196
+ underline?: boolean
197
+ strikethrough?: boolean
198
+ code?: boolean
199
+ }
200
+
201
+ /**
202
+ * Inline link
203
+ */
204
+ export interface LinkInline {
205
+ type: 'link'
206
+ url: string
207
+ children: TextNode[]
208
+ }`;
209
+ }
210
+ generateComponent(component) {
211
+ const lines = [];
212
+ lines.push(`export interface ${component.cleanName} {`);
213
+ // Add base id field (components have id in Strapi)
214
+ lines.push(' id: number');
215
+ // Add scalar attributes
216
+ for (const attr of component.attributes) {
217
+ const tsType = this.transformer.toTypeScript(attr.type, attr.required);
218
+ lines.push(` ${attr.name}: ${tsType}`);
219
+ }
220
+ // Add media fields
221
+ for (const mediaField of component.media) {
222
+ const mediaType = mediaField.multiple ? 'MediaFile[]' : 'MediaFile';
223
+ const suffix = mediaField.required ? '' : ' | null';
224
+ lines.push(` ${mediaField.name}: ${mediaType}${suffix}`);
225
+ }
226
+ // Add relations (reference by ID)
227
+ for (const rel of component.relations) {
228
+ const isArray = rel.relationType === 'oneToMany' ||
229
+ rel.relationType === 'manyToMany';
230
+ const relType = isArray
231
+ ? '{ id: number; documentId: string }[]'
232
+ : '{ id: number; documentId: string } | null';
233
+ lines.push(` ${rel.name}: ${relType}`);
234
+ }
235
+ // Add components
236
+ for (const compField of component.components) {
237
+ const compType = compField.repeatable
238
+ ? `${compField.componentType}[]`
239
+ : compField.componentType;
240
+ const suffix = compField.required ? '' : ' | null';
241
+ lines.push(` ${compField.name}: ${compType}${suffix}`);
242
+ }
243
+ // Add dynamic zones
244
+ for (const dzField of component.dynamicZones) {
245
+ const dzType = `(${dzField.componentTypes.join(' | ')})[]`;
246
+ const suffix = dzField.required ? '' : ' | null';
247
+ lines.push(` ${dzField.name}: ${dzType}${suffix}`);
248
+ }
249
+ lines.push('}');
250
+ return lines.join('\n');
251
+ }
252
+ generateComponentInput(component) {
253
+ const lines = [];
254
+ lines.push(`// Input type for creating/updating ${component.cleanName}`);
255
+ lines.push(`export interface ${component.cleanName}Input {`);
256
+ // id is optional for input (omit when creating, include when updating existing)
257
+ lines.push(' id?: number');
258
+ // Add scalar attributes (all optional)
259
+ for (const attr of component.attributes) {
260
+ const tsType = this.transformer.toTypeScript(attr.type, false); // Always optional
261
+ lines.push(` ${attr.name}?: ${tsType}`);
262
+ }
263
+ // Add media fields as ID or ID array
264
+ for (const mediaField of component.media) {
265
+ const mediaType = mediaField.multiple ? 'number[]' : 'number';
266
+ lines.push(` ${mediaField.name}?: ${mediaType} | null`);
267
+ }
268
+ // Add relations as ID or ID array
269
+ for (const rel of component.relations) {
270
+ const isArray = rel.relationType === 'oneToMany' ||
271
+ rel.relationType === 'manyToMany';
272
+ const relType = isArray ? 'number[]' : 'number';
273
+ lines.push(` ${rel.name}?: ${relType} | null`);
274
+ }
275
+ // Add nested components
276
+ for (const compField of component.components) {
277
+ const compType = compField.repeatable
278
+ ? `${compField.componentType}Input[]`
279
+ : `${compField.componentType}Input`;
280
+ lines.push(` ${compField.name}?: ${compType} | null`);
281
+ }
282
+ // Add dynamic zones
283
+ for (const dzField of component.dynamicZones) {
284
+ const dzType = `(${dzField.componentTypes.map(ct => `${ct}Input`).join(' | ')})[]`;
285
+ lines.push(` ${dzField.name}?: ${dzType} | null`);
286
+ }
287
+ lines.push('}');
288
+ return lines.join('\n');
289
+ }
290
+ generateContentType(contentType) {
291
+ const lines = [];
292
+ lines.push(`export interface ${contentType.cleanName} {`);
293
+ // Add nominal typing field to make types structurally unique
294
+ lines.push(` readonly __typename?: '${contentType.cleanName}'`);
295
+ // Add base fields
296
+ lines.push(' id: number');
297
+ lines.push(' documentId: string');
298
+ lines.push(' createdAt: string');
299
+ lines.push(' updatedAt: string');
300
+ // Add custom attributes
301
+ for (const attr of contentType.attributes) {
302
+ const tsType = this.transformer.toTypeScript(attr.type, attr.required);
303
+ lines.push(` ${attr.name}: ${tsType}`);
304
+ }
305
+ lines.push('}');
306
+ return lines.join('\n');
307
+ }
308
+ generateInputType(contentType) {
309
+ const lines = [];
310
+ lines.push(`// Input type for creating/updating ${contentType.cleanName}`);
311
+ lines.push(`export interface ${contentType.cleanName}Input {`);
312
+ // Add scalar attributes (all optional for partial updates)
313
+ for (const attr of contentType.attributes) {
314
+ const tsType = this.transformer.toTypeScript(attr.type, false); // Always optional
315
+ lines.push(` ${attr.name}?: ${tsType}`);
316
+ }
317
+ // Add media fields as ID or ID array
318
+ for (const mediaField of contentType.media) {
319
+ const mediaType = mediaField.multiple ? 'number[]' : 'number';
320
+ lines.push(` ${mediaField.name}?: ${mediaType} | null`);
321
+ }
322
+ // Add relations as ID or ID array
323
+ for (const rel of contentType.relations) {
324
+ const isArray = rel.relationType === 'oneToMany' ||
325
+ rel.relationType === 'manyToMany';
326
+ const relType = isArray ? 'number[]' : 'number';
327
+ lines.push(` ${rel.name}?: ${relType} | null`);
328
+ }
329
+ // Add components as objects (using component type, but with optional fields)
330
+ for (const compField of contentType.components) {
331
+ const compType = compField.repeatable
332
+ ? `${compField.componentType}Input[]`
333
+ : `${compField.componentType}Input`;
334
+ lines.push(` ${compField.name}?: ${compType} | null`);
335
+ }
336
+ // Add dynamic zones
337
+ for (const dzField of contentType.dynamicZones) {
338
+ const dzType = `(${dzField.componentTypes.map(ct => `${ct}Input`).join(' | ')})[]`;
339
+ lines.push(` ${dzField.name}?: ${dzType} | null`);
340
+ }
341
+ lines.push('}');
342
+ return lines.join('\n');
343
+ }
344
+ generatePopulateParams(schema) {
345
+ const lines = [];
346
+ lines.push('// ============================================');
347
+ lines.push('// PopulateParam types for type-safe populate');
348
+ lines.push('// ============================================');
349
+ lines.push('');
350
+ // Generate for components with populatable fields
351
+ for (const component of schema.components) {
352
+ const result = this.generatePopulateParamForType(component);
353
+ if (result) {
354
+ lines.push(result);
355
+ lines.push('');
356
+ }
357
+ }
358
+ // Generate for content types with populatable fields
359
+ for (const contentType of schema.contentTypes) {
360
+ const result = this.generatePopulateParamForType(contentType);
361
+ if (result) {
362
+ lines.push(result);
363
+ lines.push('');
364
+ }
365
+ }
366
+ return lines.join('\n');
367
+ }
368
+ generatePopulateParamForType(type) {
369
+ const populatableFields = [];
370
+ // Collect all populatable field entries
371
+ for (const rel of type.relations) {
372
+ const t = rel.targetType;
373
+ const targetHasPopulate = this.hasPopulatableFields(t);
374
+ const options = [`fields?: _EntityField<${t}>[]`];
375
+ if (targetHasPopulate) {
376
+ options.push(`populate?: ${t}PopulateParam | (keyof ${t}PopulateParam & string)[] | '*'`);
377
+ }
378
+ options.push(`filters?: ${t}Filters`);
379
+ options.push(`sort?: _SortValue<${t}> | _SortValue<${t}>[]`);
380
+ options.push(`limit?: number`);
381
+ options.push(`start?: number`);
382
+ populatableFields.push(` ${rel.name}?: true | { ${options.join('; ')} }`);
383
+ }
384
+ for (const media of type.media) {
385
+ populatableFields.push(` ${media.name}?: true | { fields?: (keyof MediaFile & string)[] }`);
386
+ }
387
+ for (const comp of type.components) {
388
+ const t = comp.componentType;
389
+ const targetHasPopulate = this.hasPopulatableFields(t);
390
+ const options = [`fields?: (keyof ${t} & string)[]`];
391
+ if (targetHasPopulate) {
392
+ options.push(`populate?: ${t}PopulateParam | (keyof ${t}PopulateParam & string)[] | '*'`);
393
+ }
394
+ populatableFields.push(` ${comp.name}?: true | { ${options.join('; ')} }`);
395
+ }
396
+ for (const dz of type.dynamicZones) {
397
+ // Dynamic zone: support Strapi v5 "on" fragment syntax
398
+ const onEntries = [];
399
+ for (let i = 0; i < dz.components.length; i++) {
400
+ const uid = dz.components[i];
401
+ const cleanType = dz.componentTypes[i];
402
+ const hasPopulate = this.hasPopulatableFields(cleanType);
403
+ const options = [`fields?: (keyof ${cleanType} & string)[]`];
404
+ if (hasPopulate) {
405
+ options.push(`populate?: ${cleanType}PopulateParam | (keyof ${cleanType}PopulateParam & string)[] | '*'`);
406
+ }
407
+ onEntries.push(`'${uid}'?: true | { ${options.join('; ')} }`);
408
+ }
409
+ populatableFields.push(` ${dz.name}?: true | { on?: { ${onEntries.join('; ')} } }`);
410
+ }
411
+ if (populatableFields.length === 0)
412
+ return null;
413
+ const lines = [];
414
+ lines.push(`export type ${type.cleanName}PopulateParam = {`);
415
+ lines.push(...populatableFields);
416
+ lines.push('}');
417
+ return lines.join('\n');
418
+ }
419
+ generatePayloadUtilityTypes(schema) {
420
+ const lines = [];
421
+ lines.push('// Prisma-like Payload types for populate support');
422
+ lines.push('// These types allow type-safe population of relations');
423
+ lines.push('//');
424
+ lines.push('// Usage example:');
425
+ lines.push('// type ItemWithCategory = ItemGetPayload<{ populate: { category: true } }>');
426
+ lines.push('// const items = await strapi.items.find({ populate: { category: true } }) as ItemWithCategory[]');
427
+ lines.push('//');
428
+ lines.push('// This ensures that relations are only included in the type when populate is used');
429
+ lines.push('');
430
+ // Generate GetPayload type for each component with populatable fields
431
+ for (const component of schema.components) {
432
+ const hasPopulatableFields = component.relations.length > 0 ||
433
+ component.media.length > 0 ||
434
+ component.components.length > 0 ||
435
+ component.dynamicZones.length > 0;
436
+ if (hasPopulatableFields) {
437
+ lines.push(this.generateComponentGetPayloadType(component));
438
+ lines.push('');
439
+ }
440
+ }
441
+ // Generate GetPayload type for each content type with populatable fields
442
+ for (const contentType of schema.contentTypes) {
443
+ const hasPopulatableFields = contentType.relations.length > 0 ||
444
+ contentType.media.length > 0 ||
445
+ contentType.components.length > 0 ||
446
+ contentType.dynamicZones.length > 0;
447
+ if (hasPopulatableFields) {
448
+ lines.push(this.generateGetPayloadType(contentType));
449
+ lines.push('');
450
+ }
451
+ }
452
+ return lines.join('\n');
453
+ }
454
+ generateGetPayloadType(contentType) {
455
+ const lines = [];
456
+ lines.push(`// Payload type for ${contentType.cleanName} with populate support`);
457
+ lines.push(`export type ${contentType.cleanName}GetPayload<P extends { populate?: any } = {}> =`);
458
+ lines.push(` ${contentType.cleanName} &`);
459
+ const hasPopulatableFields = contentType.relations.length > 0 ||
460
+ contentType.media.length > 0 ||
461
+ contentType.components.length > 0 ||
462
+ contentType.dynamicZones.length > 0;
463
+ if (!hasPopulatableFields) {
464
+ lines.push(` {}`);
465
+ }
466
+ else {
467
+ lines.push(` (P extends { populate: infer Pop }`);
468
+ // Branch 1: Pop extends '*' | true → populate ALL fields (1 level deep)
469
+ lines.push(` ? Pop extends '*' | true`);
470
+ lines.push(` ? {`);
471
+ this.generateAllPopulatedFields(lines, contentType);
472
+ lines.push(` }`);
473
+ // Branch 2: Pop is a string array → check field name in array values
474
+ lines.push(` : Pop extends readonly (infer _)[]`);
475
+ lines.push(` ? {`);
476
+ this.generateArrayPopulatedFields(lines, contentType);
477
+ lines.push(` }`);
478
+ // Branch 3: Pop is an object → per-field conditional populate
479
+ lines.push(` : {`);
480
+ this.generatePerFieldPopulate(lines, contentType);
481
+ lines.push(` }`);
482
+ lines.push(` : {})`);
483
+ }
484
+ return lines.join('\n');
485
+ }
486
+ /**
487
+ * Generate fields for populate: '*' | true — all populatable fields with base types
488
+ */
489
+ generateAllPopulatedFields(lines, type) {
490
+ for (const rel of type.relations) {
491
+ const isNullable = rel.relationType === 'oneToOne' ||
492
+ rel.relationType === 'manyToOne';
493
+ const isArray = rel.relationType === 'oneToMany' ||
494
+ rel.relationType === 'manyToMany';
495
+ const nullSuffix = isNullable ? ' | null' : '';
496
+ const arraySuffix = isArray ? '[]' : '';
497
+ lines.push(` ${rel.name}?: ${rel.targetType}${arraySuffix}${nullSuffix}`);
498
+ }
499
+ for (const mediaField of type.media) {
500
+ const mediaType = mediaField.multiple ? 'MediaFile[]' : 'MediaFile';
501
+ lines.push(` ${mediaField.name}?: ${mediaType}`);
502
+ }
503
+ for (const componentField of type.components) {
504
+ const arraySuffix = componentField.repeatable ? '[]' : '';
505
+ lines.push(` ${componentField.name}?: ${componentField.componentType}${arraySuffix}`);
506
+ }
507
+ for (const dzField of type.dynamicZones) {
508
+ const unionType = dzField.componentTypes.join(' | ');
509
+ lines.push(` ${dzField.name}?: (${unionType})[]`);
510
+ }
511
+ }
512
+ /**
513
+ * Generate fields for array-style populate (e.g. populate: ['category', 'image'])
514
+ * Each field checks if its name is in Pop[number]
515
+ */
516
+ generateArrayPopulatedFields(lines, type) {
517
+ for (const rel of type.relations) {
518
+ const isNullable = rel.relationType === 'oneToOne' ||
519
+ rel.relationType === 'manyToOne';
520
+ const isArray = rel.relationType === 'oneToMany' ||
521
+ rel.relationType === 'manyToMany';
522
+ const nullSuffix = isNullable ? ' | null' : '';
523
+ const arraySuffix = isArray ? '[]' : '';
524
+ lines.push(` ${rel.name}?: '${rel.name}' extends Pop[number] ? ${rel.targetType}${arraySuffix}${nullSuffix} : never`);
525
+ }
526
+ for (const mediaField of type.media) {
527
+ const mediaType = mediaField.multiple ? 'MediaFile[]' : 'MediaFile';
528
+ lines.push(` ${mediaField.name}?: '${mediaField.name}' extends Pop[number] ? ${mediaType} : never`);
529
+ }
530
+ for (const componentField of type.components) {
531
+ const arraySuffix = componentField.repeatable ? '[]' : '';
532
+ lines.push(` ${componentField.name}?: '${componentField.name}' extends Pop[number] ? ${componentField.componentType}${arraySuffix} : never`);
533
+ }
534
+ for (const dzField of type.dynamicZones) {
535
+ const unionType = dzField.componentTypes.join(' | ');
536
+ lines.push(` ${dzField.name}?: '${dzField.name}' extends Pop[number] ? (${unionType})[] : never`);
537
+ }
538
+ }
539
+ /**
540
+ * Generate per-field conditional populate for object-style populate params
541
+ */
542
+ generatePerFieldPopulate(lines, type) {
543
+ for (const rel of type.relations) {
544
+ const isNullable = rel.relationType === 'oneToOne' ||
545
+ rel.relationType === 'manyToOne';
546
+ const isArray = rel.relationType === 'oneToMany' ||
547
+ rel.relationType === 'manyToMany';
548
+ const baseType = rel.targetType;
549
+ const nullSuffix = isNullable ? ' | null' : '';
550
+ const arraySuffix = isArray ? '[]' : '';
551
+ const hasPopulate = this.hasPopulatableFields(baseType);
552
+ lines.push(` ${rel.name}?: '${rel.name}' extends keyof Pop`);
553
+ if (hasPopulate) {
554
+ lines.push(` ? _ApplyFields<Pop['${rel.name}'] extends { populate: infer NestedPop } ? ${baseType}GetPayload<{ populate: NestedPop }> : ${baseType}, ${baseType}, Pop['${rel.name}']>${arraySuffix}${nullSuffix}`);
555
+ }
556
+ else {
557
+ lines.push(` ? _ApplyFields<${baseType}, ${baseType}, Pop['${rel.name}']>${arraySuffix}${nullSuffix}`);
558
+ }
559
+ lines.push(` : never`);
560
+ }
561
+ for (const mediaField of type.media) {
562
+ const arraySuffix = mediaField.multiple ? '[]' : '';
563
+ lines.push(` ${mediaField.name}?: '${mediaField.name}' extends keyof Pop ? _ApplyFields<MediaFile, MediaFile, Pop['${mediaField.name}']>${arraySuffix} : never`);
564
+ }
565
+ for (const componentField of type.components) {
566
+ const baseType = componentField.componentType;
567
+ const arraySuffix = componentField.repeatable ? '[]' : '';
568
+ const hasPopulate = this.hasPopulatableFields(baseType);
569
+ lines.push(` ${componentField.name}?: '${componentField.name}' extends keyof Pop`);
570
+ if (hasPopulate) {
571
+ lines.push(` ? _ApplyFields<Pop['${componentField.name}'] extends { populate: infer NestedPop } ? ${baseType}GetPayload<{ populate: NestedPop }> : ${baseType}, ${baseType}, Pop['${componentField.name}']>${arraySuffix}`);
572
+ }
573
+ else {
574
+ lines.push(` ? _ApplyFields<${baseType}, ${baseType}, Pop['${componentField.name}']>${arraySuffix}`);
575
+ }
576
+ lines.push(` : never`);
577
+ }
578
+ for (const dzField of type.dynamicZones) {
579
+ const unionType = dzField.componentTypes.join(' | ');
580
+ const dzType = `(${unionType})[]`;
581
+ lines.push(` ${dzField.name}?: '${dzField.name}' extends keyof Pop ? ${dzType} : never`);
582
+ }
583
+ }
584
+ generateComponentGetPayloadType(component) {
585
+ const lines = [];
586
+ lines.push(`// Payload type for ${component.cleanName} with populate support`);
587
+ lines.push(`export type ${component.cleanName}GetPayload<P extends { populate?: any } = {}> =`);
588
+ lines.push(` ${component.cleanName} &`);
589
+ const hasPopulatableFields = component.relations.length > 0 ||
590
+ component.media.length > 0 ||
591
+ component.components.length > 0 ||
592
+ component.dynamicZones.length > 0;
593
+ if (!hasPopulatableFields) {
594
+ lines.push(` {}`);
595
+ }
596
+ else {
597
+ lines.push(` (P extends { populate: infer Pop }`);
598
+ // Branch 1: Pop extends '*' | true → populate ALL fields (1 level deep)
599
+ lines.push(` ? Pop extends '*' | true`);
600
+ lines.push(` ? {`);
601
+ this.generateAllPopulatedFields(lines, component);
602
+ lines.push(` }`);
603
+ // Branch 2: Pop is a string array → check field name in array values
604
+ lines.push(` : Pop extends readonly (infer _)[]`);
605
+ lines.push(` ? {`);
606
+ this.generateArrayPopulatedFields(lines, component);
607
+ lines.push(` }`);
608
+ // Branch 3: Pop is an object → per-field conditional populate
609
+ lines.push(` : {`);
610
+ this.generatePerFieldPopulate(lines, component);
611
+ lines.push(` }`);
612
+ lines.push(` : {})`);
613
+ }
614
+ return lines.join('\n');
615
+ }
616
+ /**
617
+ * Check if a type (by name) has populatable fields (relations, media, components, or dynamic zones)
618
+ */
619
+ hasPopulatableFields(typeName) {
620
+ if (!this.schema)
621
+ return false;
622
+ // Check in content types
623
+ const contentType = this.schema.contentTypes.find(ct => ct.cleanName === typeName);
624
+ if (contentType) {
625
+ return (contentType.relations.length > 0 ||
626
+ contentType.media.length > 0 ||
627
+ contentType.components.length > 0 ||
628
+ contentType.dynamicZones.length > 0);
629
+ }
630
+ // Check in components
631
+ const component = this.schema.components.find(comp => comp.cleanName === typeName);
632
+ if (component) {
633
+ return (component.relations.length > 0 ||
634
+ component.media.length > 0 ||
635
+ component.components.length > 0 ||
636
+ component.dynamicZones.length > 0);
637
+ }
638
+ return false;
639
+ }
640
+ }
641
+ //# sourceMappingURL=types-generator.js.map