simple-strapi 1.0.0-alpha.1
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/LICENSE +15 -0
- package/dist/client.d.ts +103 -0
- package/dist/client.js +267 -0
- package/dist/fields/component.d.ts +28 -0
- package/dist/fields/component.js +25 -0
- package/dist/fields/dynamic.d.ts +16 -0
- package/dist/fields/dynamic.js +20 -0
- package/dist/fields/enumeration.d.ts +8 -0
- package/dist/fields/enumeration.js +12 -0
- package/dist/fields/media.d.ts +41 -0
- package/dist/fields/media.js +44 -0
- package/dist/fields/number.d.ts +9 -0
- package/dist/fields/number.js +12 -0
- package/dist/fields/relation.d.ts +10 -0
- package/dist/fields/relation.js +4 -0
- package/dist/fields/richText.d.ts +32 -0
- package/dist/fields/richText.js +34 -0
- package/dist/fields/text.d.ts +8 -0
- package/dist/fields/text.js +12 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/utils/schema.d.ts +3 -0
- package/dist/utils/schema.js +59 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ernesto Bellei, hund.studio
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { InferNumber, NumberField, NumberOptions } from "./fields/number";
|
|
2
|
+
import { InferText, TextField, TextOptions } from "./fields/text";
|
|
3
|
+
import { InferRelationHasMany, RelationHasManyField, RelationHasManyOptions } from "./fields/relation";
|
|
4
|
+
import { DynamicField, DynamicOptions, InferDynamic } from "./fields/dynamic";
|
|
5
|
+
import { ComponentRepeatableField, ComponentRepeatableOptions, ComponentSingleField, ComponentSingleOptions, InferComponentRepeatable, InferComponentSingle } from "./fields/component";
|
|
6
|
+
import { InferMediaSingle, MediaSingleField, MediaSingleOptions } from "./fields/media";
|
|
7
|
+
import { EnumerationField, EnumerationOptions, InferEnumeration } from "./fields/enumeration";
|
|
8
|
+
import { InferRichTextBlocks, RichTextBlocksField, RichTextBlocksOptions } from "./fields/richText";
|
|
9
|
+
type RequestParams = Record<string, any>;
|
|
10
|
+
type EntityRequest<P = {}> = {
|
|
11
|
+
params?: RequestParams;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
} & P;
|
|
14
|
+
export type SchemaField = TextField | NumberField | RelationHasManyField | DynamicField | ComponentSingleField | ComponentRepeatableField | MediaSingleField | EnumerationField | RichTextBlocksField;
|
|
15
|
+
export type Schema = Record<string, SchemaField>;
|
|
16
|
+
export type InferSchema<S extends Schema> = {
|
|
17
|
+
[K in keyof S]: S[K] extends ["text", infer O extends TextOptions] ? InferText<O> : S[K] extends ["number", infer O extends NumberOptions] ? InferNumber<O> : S[K] extends [
|
|
18
|
+
"relation.hasMany",
|
|
19
|
+
infer R extends Schema,
|
|
20
|
+
infer O extends RelationHasManyOptions
|
|
21
|
+
] ? InferRelationHasMany<R, O> : S[K] extends [
|
|
22
|
+
"component.single",
|
|
23
|
+
infer R extends Schema,
|
|
24
|
+
infer O extends ComponentSingleOptions
|
|
25
|
+
] ? InferComponentSingle<R, O> : S[K] extends [
|
|
26
|
+
"component.repeatable",
|
|
27
|
+
infer R extends Schema,
|
|
28
|
+
infer O extends ComponentRepeatableOptions
|
|
29
|
+
] ? InferComponentRepeatable<R, O> : S[K] extends [
|
|
30
|
+
"dynamic",
|
|
31
|
+
infer B extends Record<string, Schema>,
|
|
32
|
+
infer O extends DynamicOptions
|
|
33
|
+
] ? InferDynamic<B, O> : S[K] extends ["media.single", infer O extends MediaSingleOptions] ? InferMediaSingle<O> : S[K] extends [
|
|
34
|
+
"enumeration",
|
|
35
|
+
infer V extends readonly [string, ...string[]],
|
|
36
|
+
infer O extends EnumerationOptions
|
|
37
|
+
] ? InferEnumeration<V, O> : S[K] extends ["richText.blocks", infer O extends RichTextBlocksOptions] ? InferRichTextBlocks<O> : never;
|
|
38
|
+
};
|
|
39
|
+
declare class Client {
|
|
40
|
+
private options;
|
|
41
|
+
private static headers;
|
|
42
|
+
static create(endpoint: string | URL, { auth, ...options }?: EntityRequest<{
|
|
43
|
+
auth?: {
|
|
44
|
+
email: string;
|
|
45
|
+
password: string;
|
|
46
|
+
} | string;
|
|
47
|
+
}>): Promise<Client>;
|
|
48
|
+
private static getRequestURL;
|
|
49
|
+
static getToken(auth: {
|
|
50
|
+
email: string;
|
|
51
|
+
password: string;
|
|
52
|
+
}, { origin, pathname }: {
|
|
53
|
+
pathname: string;
|
|
54
|
+
origin: string;
|
|
55
|
+
}): Promise<string>;
|
|
56
|
+
private origin;
|
|
57
|
+
private pathname;
|
|
58
|
+
private params;
|
|
59
|
+
private token;
|
|
60
|
+
private headers;
|
|
61
|
+
constructor(options: EntityRequest<{
|
|
62
|
+
origin: string;
|
|
63
|
+
pathname: string;
|
|
64
|
+
token?: string;
|
|
65
|
+
}>);
|
|
66
|
+
private getAuthorizedHeaders;
|
|
67
|
+
private populateFromSchema;
|
|
68
|
+
getSingle<S extends Schema>(pluralID: string, options: EntityRequest<{
|
|
69
|
+
schema: S;
|
|
70
|
+
populate?: any;
|
|
71
|
+
}>): Promise<{
|
|
72
|
+
data: InferSchema<S>;
|
|
73
|
+
meta: any;
|
|
74
|
+
}>;
|
|
75
|
+
getSingle(pluralID: string, options: EntityRequest<{
|
|
76
|
+
populate?: any;
|
|
77
|
+
}>): Promise<{
|
|
78
|
+
data: any;
|
|
79
|
+
meta: any;
|
|
80
|
+
}>;
|
|
81
|
+
getCollection<S extends Schema>(pluralID: string, options: EntityRequest<{
|
|
82
|
+
schema: S;
|
|
83
|
+
pagination?: false | {
|
|
84
|
+
page?: number;
|
|
85
|
+
pageSize?: number;
|
|
86
|
+
};
|
|
87
|
+
populate?: any;
|
|
88
|
+
}>): Promise<{
|
|
89
|
+
data: InferSchema<S>[];
|
|
90
|
+
meta: any;
|
|
91
|
+
}>;
|
|
92
|
+
getCollection(pluralID: string, options: EntityRequest<{
|
|
93
|
+
pagination?: false | {
|
|
94
|
+
page?: number;
|
|
95
|
+
pageSize?: number;
|
|
96
|
+
};
|
|
97
|
+
populate?: any;
|
|
98
|
+
}>): Promise<{
|
|
99
|
+
data: any[];
|
|
100
|
+
meta: any;
|
|
101
|
+
}>;
|
|
102
|
+
}
|
|
103
|
+
export default Client;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { createSimpleException, isSimpleExceptionOrFallback } from "@hund-ernesto/simple-exception";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import fetch from "node-fetch";
|
|
4
|
+
import qs from "qs";
|
|
5
|
+
import z from "zod";
|
|
6
|
+
import { schemaToParser } from "./utils/schema";
|
|
7
|
+
class Client {
|
|
8
|
+
static async create(endpoint, { auth, ...options } = {}) {
|
|
9
|
+
const endpointURL = new URL(endpoint);
|
|
10
|
+
const origin = endpointURL.origin;
|
|
11
|
+
const pathname = endpointURL.pathname;
|
|
12
|
+
let token;
|
|
13
|
+
if (auth) {
|
|
14
|
+
if (typeof auth === "object") {
|
|
15
|
+
token = await Client.getToken(auth, { origin, pathname });
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
token = auth;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return new Client({ origin, pathname, ...options, token });
|
|
22
|
+
}
|
|
23
|
+
static getRequestURL({ pathname, origin, params, }) {
|
|
24
|
+
const endpointURL = new URL(pathname, origin);
|
|
25
|
+
const queryString = qs.stringify(params, { arrayFormat: "brackets", encodeValuesOnly: true });
|
|
26
|
+
endpointURL.search = queryString;
|
|
27
|
+
return endpointURL;
|
|
28
|
+
}
|
|
29
|
+
static async getToken(auth, { origin, pathname }) {
|
|
30
|
+
try {
|
|
31
|
+
const requestURL = this.getRequestURL({
|
|
32
|
+
pathname: join(pathname, "/auth/local"),
|
|
33
|
+
origin,
|
|
34
|
+
});
|
|
35
|
+
const response = await fetch(requestURL, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: this.headers,
|
|
38
|
+
body: JSON.stringify({ identifier: auth.email, password: auth.password }),
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
throw createSimpleException({
|
|
42
|
+
code: response.status,
|
|
43
|
+
message: response.statusText,
|
|
44
|
+
type: "error",
|
|
45
|
+
source: "strapi-utils/client.ts",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const data = await response.json();
|
|
49
|
+
const { token } = z.object({ token: z.string() }).parse(data);
|
|
50
|
+
return token;
|
|
51
|
+
}
|
|
52
|
+
catch (exception) {
|
|
53
|
+
throw isSimpleExceptionOrFallback(exception);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
constructor(options) {
|
|
57
|
+
this.options = options;
|
|
58
|
+
this.params = {};
|
|
59
|
+
this.token = null;
|
|
60
|
+
this.headers = {};
|
|
61
|
+
this.getAuthorizedHeaders = () => {
|
|
62
|
+
const headers = { ...this.headers };
|
|
63
|
+
if (this.token)
|
|
64
|
+
headers.Authorization = `Bearer ${this.token}`;
|
|
65
|
+
return headers;
|
|
66
|
+
};
|
|
67
|
+
this.populateFromSchema = (shape) => {
|
|
68
|
+
const populateHasManyRelation = ([, shape]) => {
|
|
69
|
+
return this.populateFromSchema(shape);
|
|
70
|
+
};
|
|
71
|
+
const populateComponentSingle = ([, shape]) => {
|
|
72
|
+
return this.populateFromSchema(shape);
|
|
73
|
+
};
|
|
74
|
+
const populateComponentRepeatable = ([, shape]) => {
|
|
75
|
+
return this.populateFromSchema(shape);
|
|
76
|
+
};
|
|
77
|
+
const populateDynamic = ([, shape]) => {
|
|
78
|
+
const blocks = {};
|
|
79
|
+
for (const [key, field] of Object.entries(shape)) {
|
|
80
|
+
blocks[key] = { populate: this.populateFromSchema(field) };
|
|
81
|
+
if (!Object.keys(blocks[key].populate)["length"])
|
|
82
|
+
blocks[key] = true;
|
|
83
|
+
}
|
|
84
|
+
return blocks;
|
|
85
|
+
};
|
|
86
|
+
const populate = {};
|
|
87
|
+
for (const [key, field] of Object.entries(shape)) {
|
|
88
|
+
switch (field[0]) {
|
|
89
|
+
case "relation.hasMany":
|
|
90
|
+
populate[key] = { populate: populateHasManyRelation(field) };
|
|
91
|
+
if (!Object.keys(populate[key].populate)["length"])
|
|
92
|
+
populate[key] = true;
|
|
93
|
+
break;
|
|
94
|
+
case "component.single":
|
|
95
|
+
populate[key] = { populate: populateComponentSingle(field) };
|
|
96
|
+
if (!Object.keys(populate[key].populate)["length"])
|
|
97
|
+
populate[key] = true;
|
|
98
|
+
break;
|
|
99
|
+
case "component.repeatable":
|
|
100
|
+
populate[key] = { populate: populateComponentRepeatable(field) };
|
|
101
|
+
if (!Object.keys(populate[key].populate)["length"])
|
|
102
|
+
populate[key] = true;
|
|
103
|
+
break;
|
|
104
|
+
case "media.single":
|
|
105
|
+
populate[key] = { populate: true };
|
|
106
|
+
break;
|
|
107
|
+
case "dynamic":
|
|
108
|
+
populate[key] = { on: populateDynamic(field) };
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return populate;
|
|
113
|
+
};
|
|
114
|
+
const headers = (() => {
|
|
115
|
+
return { ...Client.headers, ...(this.options.headers || {}) };
|
|
116
|
+
})();
|
|
117
|
+
const params = (() => {
|
|
118
|
+
return this.options.params || this.params;
|
|
119
|
+
})();
|
|
120
|
+
const token = (() => {
|
|
121
|
+
return this.options.token || null;
|
|
122
|
+
})();
|
|
123
|
+
this.origin = this.options.origin;
|
|
124
|
+
this.pathname = this.options.pathname;
|
|
125
|
+
this.params = params;
|
|
126
|
+
this.headers = headers;
|
|
127
|
+
this.token = token;
|
|
128
|
+
}
|
|
129
|
+
async getSingle(pluralID, { params = {}, headers = {}, ...options } = {}) {
|
|
130
|
+
try {
|
|
131
|
+
if ("schema" in options) {
|
|
132
|
+
const { schema } = options;
|
|
133
|
+
if (schema) {
|
|
134
|
+
params.populate = this.populateFromSchema(schema);
|
|
135
|
+
if ("populate" in options) {
|
|
136
|
+
if (!!options.populate)
|
|
137
|
+
console.warn("⚠️ Since you provided both the 'populate' and 'schema', the 'populate' parameter will be ignored.");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if ("populate" in options) {
|
|
142
|
+
params.populate = options.populate;
|
|
143
|
+
}
|
|
144
|
+
const requestURL = Client.getRequestURL({
|
|
145
|
+
origin: this.origin,
|
|
146
|
+
pathname: join(this.pathname, pluralID),
|
|
147
|
+
params,
|
|
148
|
+
});
|
|
149
|
+
const response = await fetch(requestURL, {
|
|
150
|
+
method: "GET",
|
|
151
|
+
headers: {
|
|
152
|
+
...this.getAuthorizedHeaders(),
|
|
153
|
+
...headers,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
throw createSimpleException({
|
|
158
|
+
code: response.status,
|
|
159
|
+
message: response.statusText,
|
|
160
|
+
type: "error",
|
|
161
|
+
source: "strapi-utils/client.ts",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const { data: [data], meta, } = z.object({ data: z.array(z.any()), meta: z.any() }).parse(await response.json());
|
|
165
|
+
if (!data)
|
|
166
|
+
throw createSimpleException({ code: 404, type: "error", message: "Not found" });
|
|
167
|
+
if ("schema" in options) {
|
|
168
|
+
const { schema: shape } = options;
|
|
169
|
+
if (shape) {
|
|
170
|
+
const schema = z.object(schemaToParser(shape)).loose();
|
|
171
|
+
const result = schema.safeParse(data);
|
|
172
|
+
if (!result.success) {
|
|
173
|
+
console.warn("⚠️ Single entity parsing error");
|
|
174
|
+
console.error("🚨 Error", result.error);
|
|
175
|
+
return { data: null, meta };
|
|
176
|
+
}
|
|
177
|
+
return { data: result.data, meta };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { data, meta };
|
|
181
|
+
}
|
|
182
|
+
catch (exception) {
|
|
183
|
+
throw isSimpleExceptionOrFallback(exception);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async getCollection(pluralID, { params = {}, headers = {}, pagination = { page: 1 }, ...options } = {}) {
|
|
187
|
+
try {
|
|
188
|
+
if ("schema" in options) {
|
|
189
|
+
const { schema } = options;
|
|
190
|
+
if (schema) {
|
|
191
|
+
params.populate = this.populateFromSchema(schema);
|
|
192
|
+
if ("populate" in options) {
|
|
193
|
+
if (!!options.populate)
|
|
194
|
+
console.warn("⚠️ Since you provided both the 'populate' adnd 'schema', the 'populate' parameter will be ignored.");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else if ("populate" in options) {
|
|
199
|
+
params.populate = options.populate;
|
|
200
|
+
}
|
|
201
|
+
const fetchPage = async (page = 1, acc = []) => {
|
|
202
|
+
params.pagination = { page, pageSize: 100 };
|
|
203
|
+
if (pagination)
|
|
204
|
+
params.pagination = { ...params.pagination, ...pagination };
|
|
205
|
+
const requestURL = Client.getRequestURL({
|
|
206
|
+
origin: this.origin,
|
|
207
|
+
pathname: join(this.pathname, pluralID),
|
|
208
|
+
params,
|
|
209
|
+
});
|
|
210
|
+
const response = await fetch(requestURL, {
|
|
211
|
+
method: "GET",
|
|
212
|
+
headers: {
|
|
213
|
+
...this.getAuthorizedHeaders(),
|
|
214
|
+
...headers,
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
if (!response.ok) {
|
|
218
|
+
throw createSimpleException({
|
|
219
|
+
code: response.status,
|
|
220
|
+
message: response.statusText,
|
|
221
|
+
type: "error",
|
|
222
|
+
source: "strapi-utils/client.ts",
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
const { data, meta } = z
|
|
226
|
+
.object({ data: z.array(z.any()).catch([]), meta: z.any() })
|
|
227
|
+
.parse(await response.json());
|
|
228
|
+
const accData = [...acc, ...data];
|
|
229
|
+
if (!pagination) {
|
|
230
|
+
if (meta.pagination?.page < meta.pagination?.pageCount) {
|
|
231
|
+
return await fetchPage(meta.pagination.page + 1, accData);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return { data: accData, meta };
|
|
235
|
+
};
|
|
236
|
+
const { data, meta } = await fetchPage();
|
|
237
|
+
if ("schema" in options) {
|
|
238
|
+
const { schema: shape } = options;
|
|
239
|
+
if (shape) {
|
|
240
|
+
const schema = z.object(schemaToParser(shape)).loose();
|
|
241
|
+
const parsedData = [];
|
|
242
|
+
for (const entry of data) {
|
|
243
|
+
const result = schema.safeParse(entry);
|
|
244
|
+
if (result.success) {
|
|
245
|
+
parsedData.push(result.data);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
console.warn("⚠️ Collection parsing error on entry");
|
|
249
|
+
console.error("🚨 Error", result.error);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return { data: parsedData, meta };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return { data, meta };
|
|
256
|
+
}
|
|
257
|
+
catch (exception) {
|
|
258
|
+
throw isSimpleExceptionOrFallback(exception);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// #region STATIC
|
|
263
|
+
Client.headers = {
|
|
264
|
+
accept: "application/json",
|
|
265
|
+
"Content-Type": "application/json",
|
|
266
|
+
};
|
|
267
|
+
export default Client;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ZodType } from "zod";
|
|
2
|
+
import { InferSchema, Schema } from "../client";
|
|
3
|
+
/**
|
|
4
|
+
* SINGLE
|
|
5
|
+
*/
|
|
6
|
+
export type ComponentSingleOptions = {
|
|
7
|
+
required?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type InferComponentSingle<S extends Schema, O extends ComponentSingleOptions> = O["required"] extends true ? InferSchema<S> : InferSchema<S> | null | undefined;
|
|
10
|
+
export declare const singleSchema: (shape: Schema, opts: ComponentSingleOptions) => ZodType;
|
|
11
|
+
export type ComponentSingleField = readonly ["component.single", Schema, ComponentSingleOptions];
|
|
12
|
+
/**
|
|
13
|
+
* REPEATABLE
|
|
14
|
+
*/
|
|
15
|
+
export type ComponentRepeatableOptions = {
|
|
16
|
+
required?: boolean;
|
|
17
|
+
};
|
|
18
|
+
export type InferComponentRepeatable<S extends Schema, O extends ComponentRepeatableOptions> = O["required"] extends true ? InferSchema<S>[] : InferSchema<S>[] | null | undefined;
|
|
19
|
+
export declare const repeatableSchema: (shape: Schema, opts: ComponentRepeatableOptions) => ZodType;
|
|
20
|
+
export type ComponentRepeatableField = readonly [
|
|
21
|
+
"component.repeatable",
|
|
22
|
+
Schema,
|
|
23
|
+
ComponentRepeatableOptions
|
|
24
|
+
];
|
|
25
|
+
export declare const component: {
|
|
26
|
+
single: <S = any, O extends ComponentSingleOptions = {}>(shape: S, options?: O) => ["component.single", S, O];
|
|
27
|
+
repeatable: <S = any, O extends ComponentRepeatableOptions = {}>(shape: S, options?: O) => ["component.repeatable", S, O];
|
|
28
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { schemaToParser } from "../utils/schema";
|
|
3
|
+
const single = (shape, options = {}) => {
|
|
4
|
+
return ["component.single", shape, options];
|
|
5
|
+
};
|
|
6
|
+
export const singleSchema = (shape, opts) => {
|
|
7
|
+
let schema = z.object(schemaToParser(shape));
|
|
8
|
+
// if (opts.nullable) schema = schema.nullable();
|
|
9
|
+
// if (opts.optional) schema = schema.optional();
|
|
10
|
+
if (!opts.required)
|
|
11
|
+
schema = schema.nullable().optional();
|
|
12
|
+
return schema;
|
|
13
|
+
};
|
|
14
|
+
const repeatable = (shape, options = {}) => {
|
|
15
|
+
return ["component.repeatable", shape, options];
|
|
16
|
+
};
|
|
17
|
+
export const repeatableSchema = (shape, opts) => {
|
|
18
|
+
let schema = z.array(z.object(schemaToParser(shape)));
|
|
19
|
+
// if (opts.nullable) schema = schema.nullable();
|
|
20
|
+
// if (opts.optional) schema = schema.optional();
|
|
21
|
+
if (!opts.required)
|
|
22
|
+
schema = schema.nullable().optional();
|
|
23
|
+
return schema;
|
|
24
|
+
};
|
|
25
|
+
export const component = { single, repeatable };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { InferSchema, Schema } from "../client";
|
|
2
|
+
import { ZodType } from "zod";
|
|
3
|
+
export type DynamicOptions = {
|
|
4
|
+
nullable?: boolean;
|
|
5
|
+
optional?: boolean;
|
|
6
|
+
};
|
|
7
|
+
type DynamicBlocksUnion<B extends Record<string, Schema>> = {
|
|
8
|
+
[K in keyof B]: {
|
|
9
|
+
__component: K;
|
|
10
|
+
} & InferSchema<B[K]>;
|
|
11
|
+
}[keyof B];
|
|
12
|
+
export type InferDynamic<B extends Record<string, Schema>, O extends DynamicOptions> = O["nullable"] extends true ? O["optional"] extends true ? DynamicBlocksUnion<B>[] | null | undefined : DynamicBlocksUnion<B>[] | null : O["optional"] extends true ? DynamicBlocksUnion<B>[] | undefined : DynamicBlocksUnion<B>[];
|
|
13
|
+
export declare const dynamic: <B extends Record<string, Schema>, O extends DynamicOptions = {}>(blocks: B, options?: O) => ["dynamic", B, O];
|
|
14
|
+
export declare const dynamicSchema: (blocks: Record<string, Schema>, opts: DynamicOptions) => ZodType;
|
|
15
|
+
export type DynamicField = readonly ["dynamic", Record<string, Schema>, DynamicOptions];
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { schemaToParser } from "../utils/schema";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
export const dynamic = (blocks, options = {}) => {
|
|
4
|
+
return ["dynamic", blocks, options];
|
|
5
|
+
};
|
|
6
|
+
export const dynamicSchema = (blocks, opts) => {
|
|
7
|
+
const union = Object.entries(blocks).reduce((acc, [key, fields]) => {
|
|
8
|
+
const block = { __component: z.literal(key), ...schemaToParser(fields) };
|
|
9
|
+
acc.push(z.object(block));
|
|
10
|
+
return acc;
|
|
11
|
+
}, []);
|
|
12
|
+
if (!union["length"])
|
|
13
|
+
return z.any();
|
|
14
|
+
let schema = z.array(z.discriminatedUnion("__component", union));
|
|
15
|
+
if (opts.nullable)
|
|
16
|
+
schema = schema.nullable();
|
|
17
|
+
if (opts.optional)
|
|
18
|
+
schema = schema.optional();
|
|
19
|
+
return schema;
|
|
20
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ZodType } from "zod";
|
|
2
|
+
export type EnumerationOptions = {
|
|
3
|
+
required?: boolean;
|
|
4
|
+
};
|
|
5
|
+
export type InferEnumeration<V extends readonly [string, ...string[]], O extends EnumerationOptions> = O["required"] extends true ? V[number] : V[number] | null | undefined;
|
|
6
|
+
export declare const enumeration: <Values extends readonly [string, ...string[]], O extends EnumerationOptions = {}>(values: Values, options?: O) => ["enumeration", Values, O];
|
|
7
|
+
export declare const enumerationSchema: (values: readonly [string, ...string[]], opts: EnumerationOptions) => ZodType;
|
|
8
|
+
export type EnumerationField = ReturnType<typeof enumeration>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
export const enumeration = (values, options = {}) => {
|
|
3
|
+
return ["enumeration", values, options];
|
|
4
|
+
};
|
|
5
|
+
export const enumerationSchema = (values, opts) => {
|
|
6
|
+
let schema = z.enum([...values]);
|
|
7
|
+
// if (options.nullable) schema = schema.nullable();
|
|
8
|
+
// if (options.optional) schema = schema.optional();
|
|
9
|
+
if (!opts.required)
|
|
10
|
+
schema = schema.nullable().optional();
|
|
11
|
+
return schema;
|
|
12
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import z, { ZodType } from "zod";
|
|
2
|
+
export declare const zodMediaSchema: z.ZodObject<{
|
|
3
|
+
id: z.ZodNumber;
|
|
4
|
+
name: z.ZodString;
|
|
5
|
+
alternativeText: z.ZodNullable<z.ZodString>;
|
|
6
|
+
caption: z.ZodNullable<z.ZodString>;
|
|
7
|
+
width: z.ZodNullable<z.ZodNumber>;
|
|
8
|
+
height: z.ZodNullable<z.ZodNumber>;
|
|
9
|
+
formats: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
10
|
+
name: z.ZodString;
|
|
11
|
+
hash: z.ZodOptional<z.ZodString>;
|
|
12
|
+
ext: z.ZodOptional<z.ZodString>;
|
|
13
|
+
mime: z.ZodString;
|
|
14
|
+
path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
15
|
+
size: z.ZodNumber;
|
|
16
|
+
url: z.ZodString;
|
|
17
|
+
width: z.ZodNumber;
|
|
18
|
+
height: z.ZodNumber;
|
|
19
|
+
}, z.core.$strip>>>;
|
|
20
|
+
hash: z.ZodString;
|
|
21
|
+
ext: z.ZodString;
|
|
22
|
+
mime: z.ZodString;
|
|
23
|
+
size: z.ZodNumber;
|
|
24
|
+
url: z.ZodString;
|
|
25
|
+
previewUrl: z.ZodNullable<z.ZodString>;
|
|
26
|
+
provider: z.ZodString;
|
|
27
|
+
provider_metadata: z.ZodNullable<z.ZodUnknown>;
|
|
28
|
+
createdAt: z.ZodISODateTime;
|
|
29
|
+
updatedAt: z.ZodISODateTime;
|
|
30
|
+
}, z.core.$strip>;
|
|
31
|
+
type ZodMediaType = z.output<typeof zodMediaSchema>;
|
|
32
|
+
export type MediaSingleOptions = {
|
|
33
|
+
required?: boolean;
|
|
34
|
+
};
|
|
35
|
+
export type InferMediaSingle<O extends MediaSingleOptions> = O["required"] extends true ? ZodMediaType : ZodMediaType | null | undefined;
|
|
36
|
+
export declare const mediaSingleSchema: (opts: MediaSingleOptions) => ZodType;
|
|
37
|
+
export type MediaSingleField = readonly ["media.single", MediaSingleOptions];
|
|
38
|
+
export declare const media: {
|
|
39
|
+
single: <O extends MediaSingleOptions = {}>(options?: O) => ["media.single", O];
|
|
40
|
+
};
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
export const zodMediaSchema = z.object({
|
|
3
|
+
id: z.number(),
|
|
4
|
+
name: z.string(),
|
|
5
|
+
alternativeText: z.string().nullable(),
|
|
6
|
+
caption: z.string().nullable(),
|
|
7
|
+
width: z.number().nullable(),
|
|
8
|
+
height: z.number().nullable(),
|
|
9
|
+
formats: z
|
|
10
|
+
.record(z.string(), z.object({
|
|
11
|
+
name: z.string(),
|
|
12
|
+
hash: z.string().optional(),
|
|
13
|
+
ext: z.string().optional(),
|
|
14
|
+
mime: z.string(),
|
|
15
|
+
path: z.string().nullable().optional(),
|
|
16
|
+
size: z.number(),
|
|
17
|
+
url: z.string(),
|
|
18
|
+
width: z.number(),
|
|
19
|
+
height: z.number(),
|
|
20
|
+
}))
|
|
21
|
+
.optional(),
|
|
22
|
+
hash: z.string(),
|
|
23
|
+
ext: z.string(),
|
|
24
|
+
mime: z.string(),
|
|
25
|
+
size: z.number(),
|
|
26
|
+
url: z.string(),
|
|
27
|
+
previewUrl: z.string().nullable(),
|
|
28
|
+
provider: z.string(),
|
|
29
|
+
provider_metadata: z.unknown().nullable(),
|
|
30
|
+
createdAt: z.iso.datetime(),
|
|
31
|
+
updatedAt: z.iso.datetime(),
|
|
32
|
+
});
|
|
33
|
+
const single = (options = {}) => {
|
|
34
|
+
return ["media.single", options];
|
|
35
|
+
};
|
|
36
|
+
export const mediaSingleSchema = (opts) => {
|
|
37
|
+
let schema = zodMediaSchema;
|
|
38
|
+
// if (opts.nullable) schema = schema.nullable();
|
|
39
|
+
// if (opts.optional) schema = schema.optional();
|
|
40
|
+
if (!opts.required)
|
|
41
|
+
schema = schema.nullable().optional();
|
|
42
|
+
return schema;
|
|
43
|
+
};
|
|
44
|
+
export const media = { single };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ZodType } from "zod";
|
|
2
|
+
export type NumberOptions = {
|
|
3
|
+
nullable?: boolean;
|
|
4
|
+
optional?: boolean;
|
|
5
|
+
};
|
|
6
|
+
export type InferNumber<O extends NumberOptions> = O["nullable"] extends true ? O["optional"] extends true ? number | null | undefined : number | null : O["optional"] extends true ? number | undefined : number;
|
|
7
|
+
export declare const number: <O extends NumberOptions = {}>(options?: O) => ["number", O];
|
|
8
|
+
export declare const numberSchema: (opts: NumberOptions) => ZodType;
|
|
9
|
+
export type NumberField = ReturnType<typeof number>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
export const number = (options = {}) => {
|
|
3
|
+
return ["number", options];
|
|
4
|
+
};
|
|
5
|
+
export const numberSchema = (opts) => {
|
|
6
|
+
let schema = z.number();
|
|
7
|
+
if (opts.nullable)
|
|
8
|
+
schema = schema.nullable();
|
|
9
|
+
if (opts.optional)
|
|
10
|
+
schema = schema.optional();
|
|
11
|
+
return schema;
|
|
12
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { InferSchema, Schema } from "../client";
|
|
2
|
+
export type RelationHasManyOptions = {
|
|
3
|
+
nullable?: boolean;
|
|
4
|
+
optional?: boolean;
|
|
5
|
+
};
|
|
6
|
+
export type InferRelationHasMany<S extends Schema, O extends RelationHasManyOptions> = O["nullable"] extends true ? O["optional"] extends true ? InferSchema<S>[] | null | undefined : InferSchema<S>[] | null : O["optional"] extends true ? InferSchema<S>[] | undefined : InferSchema<S>[];
|
|
7
|
+
export type RelationHasManyField = readonly ["relation.hasMany", Schema, RelationHasManyOptions];
|
|
8
|
+
export declare const relation: {
|
|
9
|
+
hasMany: <S = any, O extends RelationHasManyOptions = {}>(shape: S, options?: O) => ["relation.hasMany", S, O];
|
|
10
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import z, { ZodType } from "zod";
|
|
2
|
+
type TextChild = {
|
|
3
|
+
type: "text";
|
|
4
|
+
text: string;
|
|
5
|
+
bold?: boolean;
|
|
6
|
+
italic?: boolean;
|
|
7
|
+
underline?: boolean;
|
|
8
|
+
strikethrough?: boolean;
|
|
9
|
+
code?: boolean;
|
|
10
|
+
};
|
|
11
|
+
type LinkChild = {
|
|
12
|
+
type: "link";
|
|
13
|
+
url: string;
|
|
14
|
+
children: ParagraphChild[];
|
|
15
|
+
};
|
|
16
|
+
export type ParagraphChild = TextChild | LinkChild;
|
|
17
|
+
export declare const paragraphChild: z.ZodType<ParagraphChild>;
|
|
18
|
+
export declare const zodRichTextBlocksSchema: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
19
|
+
type: z.ZodLiteral<"paragraph">;
|
|
20
|
+
children: z.ZodArray<z.ZodType<ParagraphChild, unknown, z.core.$ZodTypeInternals<ParagraphChild, unknown>>>;
|
|
21
|
+
}, z.core.$strip>]>>;
|
|
22
|
+
type ZodRichTextBlocksType = z.output<typeof zodRichTextBlocksSchema>;
|
|
23
|
+
export type RichTextBlocksOptions = {
|
|
24
|
+
required?: boolean;
|
|
25
|
+
};
|
|
26
|
+
export type InferRichTextBlocks<O extends RichTextBlocksOptions> = O["required"] extends true ? ZodRichTextBlocksType : ZodRichTextBlocksType | null | undefined;
|
|
27
|
+
export declare const richTextBlocksSchema: (opts: RichTextBlocksOptions) => ZodType;
|
|
28
|
+
export type RichTextBlocksField = readonly ["richText.blocks", RichTextBlocksOptions];
|
|
29
|
+
export declare const richText: {
|
|
30
|
+
blocks: <O extends RichTextBlocksOptions = {}>(options?: O) => ["richText.blocks", O];
|
|
31
|
+
};
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
export const paragraphChild = z.lazy(() => z.discriminatedUnion("type", [
|
|
3
|
+
z.object({
|
|
4
|
+
type: z.literal("text"),
|
|
5
|
+
text: z.string(),
|
|
6
|
+
bold: z.boolean().optional(),
|
|
7
|
+
italic: z.boolean().optional(),
|
|
8
|
+
underline: z.boolean().optional(),
|
|
9
|
+
strikethrough: z.boolean().optional(),
|
|
10
|
+
code: z.boolean().optional(),
|
|
11
|
+
}),
|
|
12
|
+
z.object({
|
|
13
|
+
type: z.literal("link"),
|
|
14
|
+
url: z.string(),
|
|
15
|
+
children: z.array(paragraphChild),
|
|
16
|
+
}),
|
|
17
|
+
]));
|
|
18
|
+
const paragraphBlock = z.object({
|
|
19
|
+
type: z.literal("paragraph"),
|
|
20
|
+
children: z.array(paragraphChild),
|
|
21
|
+
});
|
|
22
|
+
export const zodRichTextBlocksSchema = z.array(z.union([paragraphBlock]));
|
|
23
|
+
const blocks = (options = {}) => {
|
|
24
|
+
return ["richText.blocks", options];
|
|
25
|
+
};
|
|
26
|
+
export const richTextBlocksSchema = (opts) => {
|
|
27
|
+
let schema = zodRichTextBlocksSchema;
|
|
28
|
+
// if (opts.nullable) schema = schema.nullable();
|
|
29
|
+
// if (opts.optional) schema = schema.optional();
|
|
30
|
+
if (!opts.required)
|
|
31
|
+
schema = schema.nullable().optional();
|
|
32
|
+
return schema;
|
|
33
|
+
};
|
|
34
|
+
export const richText = { blocks };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ZodType } from "zod";
|
|
2
|
+
export type TextOptions = {
|
|
3
|
+
required?: boolean;
|
|
4
|
+
};
|
|
5
|
+
export type InferText<O extends TextOptions> = O["required"] extends true ? string : string | null | undefined;
|
|
6
|
+
export declare const text: <O extends TextOptions = {}>(options?: O) => ["text", O];
|
|
7
|
+
export declare const textSchema: (opts: TextOptions) => ZodType;
|
|
8
|
+
export type TextField = ReturnType<typeof text>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
export const text = (options = {}) => {
|
|
3
|
+
return ["text", options];
|
|
4
|
+
};
|
|
5
|
+
export const textSchema = (opts) => {
|
|
6
|
+
let schema = z.string();
|
|
7
|
+
// if (opts.nullable) schema = schema.nullable();
|
|
8
|
+
// if (opts.optional) schema = schema.optional();
|
|
9
|
+
if (!opts.required)
|
|
10
|
+
schema = schema.nullable().optional();
|
|
11
|
+
return schema;
|
|
12
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as StrapiClient } from "./client";
|
|
2
|
+
export * from "./fields/text";
|
|
3
|
+
export * from "./fields/number";
|
|
4
|
+
export * from "./fields/relation";
|
|
5
|
+
export * from "./fields/dynamic";
|
|
6
|
+
export * from "./fields/component";
|
|
7
|
+
export * from "./fields/media";
|
|
8
|
+
export * from "./fields/enumeration";
|
|
9
|
+
export * from "./fields/richText";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as StrapiClient } from "./client";
|
|
2
|
+
export * from "./fields/text";
|
|
3
|
+
export * from "./fields/number";
|
|
4
|
+
export * from "./fields/relation";
|
|
5
|
+
export * from "./fields/dynamic";
|
|
6
|
+
export * from "./fields/component";
|
|
7
|
+
export * from "./fields/media";
|
|
8
|
+
export * from "./fields/enumeration";
|
|
9
|
+
export * from "./fields/richText";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { dynamicSchema } from "../fields/dynamic";
|
|
2
|
+
import { mediaSingleSchema } from "../fields/media";
|
|
3
|
+
import { numberSchema } from "../fields/number";
|
|
4
|
+
import { repeatableSchema, singleSchema } from "../fields/component";
|
|
5
|
+
import { textSchema } from "../fields/text";
|
|
6
|
+
import z from "zod";
|
|
7
|
+
import { enumerationSchema } from "../fields/enumeration";
|
|
8
|
+
import { richTextBlocksSchema } from "../fields/richText";
|
|
9
|
+
export const schemaToParser = (schema) => {
|
|
10
|
+
const shape = {};
|
|
11
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
12
|
+
switch (field[0]) {
|
|
13
|
+
case "text": {
|
|
14
|
+
const [, args] = field;
|
|
15
|
+
shape[key] = textSchema(args);
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
case "number": {
|
|
19
|
+
const [, args] = field;
|
|
20
|
+
shape[key] = numberSchema(args);
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
case "dynamic": {
|
|
24
|
+
const [, ...args] = field;
|
|
25
|
+
shape[key] = dynamicSchema(...args);
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
case "component.single": {
|
|
29
|
+
const [, shapeDef, options] = field;
|
|
30
|
+
shape[key] = singleSchema(shapeDef, options);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case "component.repeatable": {
|
|
34
|
+
const [, shapeDef, options] = field;
|
|
35
|
+
shape[key] = repeatableSchema(shapeDef, options);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case "media.single": {
|
|
39
|
+
const [, args] = field;
|
|
40
|
+
shape[key] = mediaSingleSchema(args);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case "enumeration": {
|
|
44
|
+
const [, values, options] = field;
|
|
45
|
+
shape[key] = enumerationSchema(values, options);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case "richText.blocks": {
|
|
49
|
+
const [, args] = field;
|
|
50
|
+
shape[key] = richTextBlocksSchema(args);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
default: {
|
|
54
|
+
shape[key] = z.any();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return shape;
|
|
59
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "simple-strapi",
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"readme": "docs/NPM.md",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/hund-studio/simple-strapi.git"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/hund-studio/simple-strapi/issues"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/hund-studio/simple-strapi#readme",
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"dev": "tsc --watch",
|
|
25
|
+
"publish:prerelease": "tsx scripts/publish.ts prerelease alpha",
|
|
26
|
+
"publish:prepatch": "tsx scripts/publish.ts prepatch alpha",
|
|
27
|
+
"publish:patch": "tsx scripts/publish.ts patch",
|
|
28
|
+
"publish:preminor": "tsx scripts/publish.ts preminor alpha",
|
|
29
|
+
"publish:minor": "tsx scripts/publish.ts minor",
|
|
30
|
+
"publish:premajor": "tsx scripts/publish.ts premajor alpha",
|
|
31
|
+
"publish:major": "tsx scripts/publish.ts major",
|
|
32
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"keywords": [],
|
|
38
|
+
"author": "",
|
|
39
|
+
"license": "ISC",
|
|
40
|
+
"description": "",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"node-fetch": "^3.3.2",
|
|
43
|
+
"qs": "^6.14.0",
|
|
44
|
+
"simple-exception": "^1.0.0-alpha.1",
|
|
45
|
+
"zod": "^4.0.5"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^24.0.14",
|
|
49
|
+
"@types/qs": "^6.14.0",
|
|
50
|
+
"tsx": "^4.20.3",
|
|
51
|
+
"typescript": "^5.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|