@abapify/speci 0.0.0 β 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +463 -2
- package/package.json +36 -3
package/README.md
CHANGED
|
@@ -1,3 +1,464 @@
|
|
|
1
|
-
#
|
|
1
|
+
# speci
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/abapify/adt-cli/pkgs/npm/%40abapify%2Fspeci)
|
|
4
|
+
|
|
5
|
+
**Minimal arrow-function-based contract specification system for TypeScript**
|
|
6
|
+
|
|
7
|
+
Zero decorators. Zero DSL. Zero dependencies. Just TypeScript arrow functions.
|
|
8
|
+
|
|
9
|
+
Part of the **ADT Toolkit** foundation - see [main README](../../README.md) for architecture overview.
|
|
10
|
+
|
|
11
|
+
## Philosophy
|
|
12
|
+
|
|
13
|
+
An endpoint = an arrow function whose parameters define the contract and whose return value defines the operation.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { http } from '@abapify/speci/rest';
|
|
17
|
+
|
|
18
|
+
// Shortcut syntax - super clean!
|
|
19
|
+
const updateUser = (id: string, user: UserInput) =>
|
|
20
|
+
http.put<User>(`/users/${id}`, user);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This is extremely expressive while staying minimal and TypeScript-native. Choose between shortcut syntax for simplicity or full syntax for control.
|
|
24
|
+
|
|
25
|
+
## Developed with ts-xsd
|
|
26
|
+
|
|
27
|
+
Speci was developed in tight combination with [ts-xsd](../ts-xsd) to support **XML-based contracts**. While most REST libraries assume JSON, Speci's `Serializable<T>` interface enables seamless integration with XML schemas:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { parse, build, type XsdSchema, type InferXsd } from 'ts-xsd';
|
|
31
|
+
import { http } from '@abapify/speci/rest';
|
|
32
|
+
|
|
33
|
+
// ts-xsd schema with parse/build
|
|
34
|
+
const TransportSchema = {
|
|
35
|
+
/* ... */
|
|
36
|
+
} as const satisfies XsdSchema;
|
|
37
|
+
type Transport = InferXsd<typeof TransportSchema>;
|
|
38
|
+
|
|
39
|
+
const transportApi = {
|
|
40
|
+
// Speci infers types from ts-xsd's Serializable interface
|
|
41
|
+
create: (transport: Transport) =>
|
|
42
|
+
http.post<Transport>('/transports', transport),
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This makes Speci ideal for enterprise APIs that use XML (SAP ADT, SOAP services, etc.) while remaining fully compatible with JSON-based schemas like Zod.
|
|
47
|
+
|
|
48
|
+
## Modular Architecture
|
|
49
|
+
|
|
50
|
+
Speci is organized into protocol-specific modules:
|
|
51
|
+
|
|
52
|
+
- **`speci`** - Core types and utilities (protocol-agnostic)
|
|
53
|
+
- **`speci/rest`** - REST API (helpers, types, client generation)
|
|
54
|
+
- **`speci/openapi`** - OpenAPI generation (planned)
|
|
55
|
+
- **`speci/cli`** - CLI generation (planned)
|
|
56
|
+
- **`speci/graphql`** - GraphQL (planned)
|
|
57
|
+
- **`speci/grpc`** - gRPC (planned)
|
|
58
|
+
|
|
59
|
+
Each protocol module is self-contained with its own types, helpers, and client generation.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install speci
|
|
65
|
+
# or
|
|
66
|
+
bun add speci
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Quick Start
|
|
70
|
+
|
|
71
|
+
### 1. Define Your Contract
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { http, type RestContract } from '@abapify/speci/rest';
|
|
75
|
+
|
|
76
|
+
// Define schemas (use any schema library: Zod, JSON Schema, etc.)
|
|
77
|
+
interface User {
|
|
78
|
+
id: string;
|
|
79
|
+
name: string;
|
|
80
|
+
email: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface CreateUserInput {
|
|
84
|
+
name: string;
|
|
85
|
+
email: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface ErrorResponse {
|
|
89
|
+
error: string;
|
|
90
|
+
message: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Define your API contract - choose your style!
|
|
94
|
+
export const api = {
|
|
95
|
+
users: {
|
|
96
|
+
// Shortcut syntax - just specify the success type
|
|
97
|
+
list: () => http.get<User[]>('/users'),
|
|
98
|
+
|
|
99
|
+
// Full syntax - explicit control over all responses
|
|
100
|
+
get: (id: string) =>
|
|
101
|
+
http.get(`/users/${id}`, {
|
|
102
|
+
responses: {
|
|
103
|
+
200: undefined as unknown as User,
|
|
104
|
+
404: undefined as unknown as ErrorResponse,
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
|
|
108
|
+
// Shortcut - pass body directly
|
|
109
|
+
create: (user: CreateUserInput) => http.post<User>('/users', user),
|
|
110
|
+
|
|
111
|
+
// Full syntax with body and responses
|
|
112
|
+
update: (id: string, user: Partial<User>) =>
|
|
113
|
+
http.put(`/users/${id}`, {
|
|
114
|
+
body: user,
|
|
115
|
+
responses: {
|
|
116
|
+
200: undefined as unknown as User,
|
|
117
|
+
404: undefined as unknown as ErrorResponse,
|
|
118
|
+
},
|
|
119
|
+
}),
|
|
120
|
+
|
|
121
|
+
// Shortcut - defaults to 204 response
|
|
122
|
+
delete: (id: string) => http.delete(`/users/${id}`),
|
|
123
|
+
},
|
|
124
|
+
} satisfies RestContract;
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 2. Generate a Typed Client
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import {
|
|
131
|
+
createClient,
|
|
132
|
+
createFetchAdapter,
|
|
133
|
+
HttpError,
|
|
134
|
+
} from '@abapify/speci/rest';
|
|
135
|
+
|
|
136
|
+
const client = createClient(api, {
|
|
137
|
+
baseUrl: 'https://api.example.com',
|
|
138
|
+
adapter: createFetchAdapter(),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Use it with full type safety - returns only success types
|
|
142
|
+
const users = await client.users.list(); // Type: User[]
|
|
143
|
+
const user = await client.users.get('123'); // Type: User
|
|
144
|
+
|
|
145
|
+
// Errors are thrown as HttpError with typed payloads
|
|
146
|
+
try {
|
|
147
|
+
const user = await client.users.get('999');
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if (client.users.get.isError(error)) {
|
|
150
|
+
// error.payload is typed as ErrorResponse!
|
|
151
|
+
console.error(error.status, error.payload.error);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Core Concepts
|
|
157
|
+
|
|
158
|
+
### Endpoint Descriptors
|
|
159
|
+
|
|
160
|
+
Every endpoint is defined by an arrow function that returns a descriptor:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const endpoint = (...params) => ({
|
|
164
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS',
|
|
165
|
+
path: '/path/with/${params}',
|
|
166
|
+
body: any, // Request body schema
|
|
167
|
+
query: any, // Query parameters schema
|
|
168
|
+
headers: any, // Headers schema
|
|
169
|
+
responses: {
|
|
170
|
+
// Response schemas by status code
|
|
171
|
+
200: SuccessSchema,
|
|
172
|
+
400: ErrorSchema,
|
|
173
|
+
},
|
|
174
|
+
metadata: {
|
|
175
|
+
// Optional metadata
|
|
176
|
+
description: 'Endpoint description',
|
|
177
|
+
tags: ['user', 'admin'],
|
|
178
|
+
deprecated: false,
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### HTTP Helper Object
|
|
184
|
+
|
|
185
|
+
The `@abapify/speci/rest` module provides an `http` object with all HTTP methods:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { http } from '@abapify/speci/rest';
|
|
189
|
+
|
|
190
|
+
// Shortcut syntax - super clean!
|
|
191
|
+
http.get<SuccessType>(path); // GET with 200 response
|
|
192
|
+
http.post<SuccessType>(path, body); // POST with 201 response
|
|
193
|
+
http.put<SuccessType>(path, body); // PUT with 200 response
|
|
194
|
+
http.patch<SuccessType>(path, body); // PATCH with 200 response
|
|
195
|
+
http.delete(path); // DELETE with 204 response
|
|
196
|
+
|
|
197
|
+
// Full syntax - explicit control
|
|
198
|
+
http.get(path, { responses: { 200: Type, 404: Error } });
|
|
199
|
+
http.post(path, { body, responses: { 201: Type, 400: Error } });
|
|
200
|
+
|
|
201
|
+
// Examples
|
|
202
|
+
const getUser = (id: string) => http.get<User>(`/users/${id}`);
|
|
203
|
+
const createUser = (user: CreateUserInput) => http.post<User>('/users', user);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Why `http` object?** The `delete` keyword is reserved in JavaScript, so we use `http.delete` instead of a standalone `delete` function.
|
|
207
|
+
|
|
208
|
+
### Schema Support
|
|
209
|
+
|
|
210
|
+
Speci supports **any schema library** - Zod, JSON Schema, custom schemas, etc. Use the `schema()` helper for clean syntax:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { z } from 'zod';
|
|
214
|
+
import { schema } from '@abapify/speci/rest';
|
|
215
|
+
|
|
216
|
+
// Zod schemas
|
|
217
|
+
const UserSchema = z.object({
|
|
218
|
+
id: z.string(),
|
|
219
|
+
name: z.string(),
|
|
220
|
+
email: z.string().email(),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
type User = z.infer<typeof UserSchema>;
|
|
224
|
+
|
|
225
|
+
const api = {
|
|
226
|
+
getUser: (id: string) =>
|
|
227
|
+
http.get(`/users/${id}`, {
|
|
228
|
+
responses: { 200: schema(UserSchema, {} as User) }, // β
Schema + type
|
|
229
|
+
}),
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Custom schemas (e.g., XML schemas for ADT)
|
|
233
|
+
const ClassSchema = {
|
|
234
|
+
element: 'class',
|
|
235
|
+
attributes: ['name', 'type'],
|
|
236
|
+
// ... your schema definition
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
interface ClassXml {
|
|
240
|
+
name: string;
|
|
241
|
+
type: string;
|
|
242
|
+
// ... type definition
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const adtApi = {
|
|
246
|
+
getClass: (name: string) =>
|
|
247
|
+
http.get(`/classes/${name}`, {
|
|
248
|
+
responses: { 200: schema(ClassSchema, {} as ClassXml) }, // β
Schema + type
|
|
249
|
+
}),
|
|
250
|
+
};
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**How it works:**
|
|
254
|
+
|
|
255
|
+
- `schema(schemaObject, {} as Type)` provides both runtime schema and compile-time type
|
|
256
|
+
- Your **adapter** sees the schema object and uses it to parse/validate
|
|
257
|
+
- TypeScript sees the type for full type safety
|
|
258
|
+
- Type-only assertions still work: `undefined as unknown as Type`
|
|
259
|
+
|
|
260
|
+
This makes Speci **schema-agnostic** - use whatever validation library you prefer!
|
|
261
|
+
|
|
262
|
+
### Global Error Responses
|
|
263
|
+
|
|
264
|
+
Avoid repeating error types across all endpoints:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import { createHttp } from '@abapify/speci/rest';
|
|
268
|
+
|
|
269
|
+
// 1. Define global error responses
|
|
270
|
+
const globalErrors = {
|
|
271
|
+
400: undefined as unknown as ApiError,
|
|
272
|
+
401: undefined as unknown as ApiError,
|
|
273
|
+
403: undefined as unknown as ApiError,
|
|
274
|
+
404: undefined as unknown as ApiError,
|
|
275
|
+
500: undefined as unknown as ApiError,
|
|
276
|
+
} as const;
|
|
277
|
+
|
|
278
|
+
// 2. Create http instance with global errors
|
|
279
|
+
const api = createHttp(globalErrors);
|
|
280
|
+
|
|
281
|
+
// 3. Now only specify success responses!
|
|
282
|
+
const userApi = {
|
|
283
|
+
list: () => api.get<User[]>('/users'),
|
|
284
|
+
// 400, 401, 403, 404, 500 automatically added!
|
|
285
|
+
|
|
286
|
+
get: (id: string) => api.get<User>(`/users/${id}`),
|
|
287
|
+
// Global errors merged automatically
|
|
288
|
+
|
|
289
|
+
create: (user: CreateUserInput) => api.post<User>('/users', user),
|
|
290
|
+
// All endpoints get global errors
|
|
291
|
+
};
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Path Parameters
|
|
295
|
+
|
|
296
|
+
Path parameters are automatically extracted from template literals:
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
const getPost = (userId: string, postId: string) =>
|
|
300
|
+
http.get<Post>(`/users/${userId}/posts/${postId}`);
|
|
301
|
+
|
|
302
|
+
// Speci automatically maps:
|
|
303
|
+
// - First param (userId) β ${userId} in path
|
|
304
|
+
// - Second param (postId) β ${postId} in path
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Error Handling
|
|
308
|
+
|
|
309
|
+
Errors are thrown as `HttpError` with typed payloads:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
import { HttpError } from '@abapify/speci/rest';
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const user = await client.users.get('123');
|
|
316
|
+
// user is typed as User (not User | ErrorResponse)
|
|
317
|
+
} catch (error) {
|
|
318
|
+
// Option 1: Use endpoint-specific type guard
|
|
319
|
+
if (client.users.get.isError(error)) {
|
|
320
|
+
// error.payload is typed as ErrorResponse
|
|
321
|
+
console.error(`HTTP ${error.status}:`, error.payload.error);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Option 2: Generic HttpError check
|
|
325
|
+
else if (error instanceof HttpError) {
|
|
326
|
+
console.error(`HTTP ${error.status}:`, error.payload);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Option 3: Network or other errors
|
|
330
|
+
else {
|
|
331
|
+
console.error('Network error:', error);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Custom HTTP Adapters
|
|
337
|
+
|
|
338
|
+
Speci is adapter-agnostic. Bring your own HTTP client:
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import type { HttpAdapter } from '@abapify/speci/rest';
|
|
342
|
+
|
|
343
|
+
const myAdapter: HttpAdapter = {
|
|
344
|
+
async request({ method, url, body, query, headers }) {
|
|
345
|
+
// Use any HTTP client: axios, got, ky, wretch, etc.
|
|
346
|
+
return await yourHttpClient.request({ method, url, body, query, headers });
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const client = createClient(api, {
|
|
351
|
+
baseUrl: 'https://api.example.com',
|
|
352
|
+
adapter: myAdapter,
|
|
353
|
+
});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Interceptors
|
|
357
|
+
|
|
358
|
+
Add request/response/error interceptors:
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
const client = createClient(api, {
|
|
362
|
+
baseUrl: 'https://api.example.com',
|
|
363
|
+
adapter: createFetchAdapter(),
|
|
364
|
+
|
|
365
|
+
// Add auth token to all requests
|
|
366
|
+
onRequest: async (options) => ({
|
|
367
|
+
...options,
|
|
368
|
+
headers: {
|
|
369
|
+
...options.headers,
|
|
370
|
+
Authorization: `Bearer ${getToken()}`,
|
|
371
|
+
},
|
|
372
|
+
}),
|
|
373
|
+
|
|
374
|
+
// Transform responses
|
|
375
|
+
onResponse: async (response) => {
|
|
376
|
+
console.log('Response:', response);
|
|
377
|
+
return response;
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
// Handle errors
|
|
381
|
+
onError: async (error) => {
|
|
382
|
+
console.error('Error:', error);
|
|
383
|
+
throw error;
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## What Speci Can Generate
|
|
389
|
+
|
|
390
|
+
From your arrow-function contracts, Speci can generate:
|
|
391
|
+
|
|
392
|
+
- β
**Typed clients** (implemented)
|
|
393
|
+
- π§ **Server routing** (planned)
|
|
394
|
+
- π§ **OpenAPI specs** (planned)
|
|
395
|
+
- π§ **Mock servers** (planned)
|
|
396
|
+
- π§ **Test fixtures** (planned)
|
|
397
|
+
- π§ **CLI tools** (planned)
|
|
398
|
+
- π§ **GraphQL schemas** (planned)
|
|
399
|
+
- π§ **gRPC definitions** (planned)
|
|
400
|
+
|
|
401
|
+
## Why Arrow Functions?
|
|
402
|
+
|
|
403
|
+
β
**No TypeScript AST parsing** - TS gives you function types natively
|
|
404
|
+
β
**No decorators** - They're optional syntactic sugar
|
|
405
|
+
β
**No template-literal parsing** - Simple variable extraction
|
|
406
|
+
β
**Perfectly readable** - Looks like ordinary domain code
|
|
407
|
+
β
**Fully expressible** - All REST/HATEOAS/gRPC concepts can be wrapped
|
|
408
|
+
β
**Zero framework coupling** - Pure TypeScript
|
|
409
|
+
|
|
410
|
+
## Coming from ts-rest?
|
|
411
|
+
|
|
412
|
+
Speci uses the same contract-first philosophy, but with arrow functions:
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
// ts-rest
|
|
416
|
+
const contract = c.router({
|
|
417
|
+
getUser: {
|
|
418
|
+
method: 'GET',
|
|
419
|
+
path: '/users/:id',
|
|
420
|
+
responses: { 200: UserSchema },
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Speci
|
|
425
|
+
const contract = {
|
|
426
|
+
getUser: (id: string) => http.get<User>(`/users/${id}`),
|
|
427
|
+
};
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Same type safety, less boilerplate. Choose ts-rest for maturity, Speci for simplicity and XML support.
|
|
431
|
+
|
|
432
|
+
## Comparison with ts-rest
|
|
433
|
+
|
|
434
|
+
[ts-rest](https://ts-rest.com/) is an excellent, mature library for type-safe REST contracts and we recommend it for most use cases. Speci covers specific scenarios where you need: **dynamic routing as functions** (endpoints defined as arrow functions with parameters), **schema flexibility** (any schema library via adapters, not just Zod), and a **flexible plugin system** for custom extensions. If ts-rest's contract-first approach works for you, use itβit's battle-tested and production-ready.
|
|
435
|
+
|
|
436
|
+
## Comparison with Other Tools
|
|
437
|
+
|
|
438
|
+
| Feature | Speci | ts-rest | tRPC | OpenAPI |
|
|
439
|
+
| ------------------ | --------------- | ----------- | ---------- | ---------- |
|
|
440
|
+
| **Syntax** | Arrow functions | Builder API | Procedures | YAML/JSON |
|
|
441
|
+
| **Type Safety** | β
Full | β
Full | β
Full | β οΈ Codegen |
|
|
442
|
+
| **Dependencies** | 0 | Few | Many | Many |
|
|
443
|
+
| **Learning Curve** | Minimal | Low | Medium | High |
|
|
444
|
+
| **Client** | β
Yes | β
Yes | β
Yes | β
Yes |
|
|
445
|
+
| **Server** | π§ Planned | β
Yes | β
Yes | β οΈ Partial |
|
|
446
|
+
| **Validation** | π§ Planned | β
Zod | β
Zod | β οΈ Varies |
|
|
447
|
+
|
|
448
|
+
## Documentation
|
|
449
|
+
|
|
450
|
+
- [Body Parameter Inference](./docs/body-inference.md) - Automatic body type inference from schemas
|
|
451
|
+
|
|
452
|
+
## License
|
|
453
|
+
|
|
454
|
+
MIT
|
|
455
|
+
|
|
456
|
+
## Contributing
|
|
457
|
+
|
|
458
|
+
Contributions welcome! This is v0.1 - the minimal viable core. Future versions will add:
|
|
459
|
+
|
|
460
|
+
- Server adapters (Express, Fastify, Hono, etc.)
|
|
461
|
+
- OpenAPI generation
|
|
462
|
+
- Mock server generation
|
|
463
|
+
- Schema validation (Zod, JSON Schema, etc.)
|
|
464
|
+
- And more!
|
package/package.json
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abapify/speci",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
|
|
3
|
+
"version": "0.3.4",
|
|
4
|
+
"description": "Minimal arrow-function-based contract specification system for TypeScript",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"types": "./dist/index.d.mts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./dist/index.mjs",
|
|
13
|
+
"./rest": "./dist/rest/index.mjs",
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"api",
|
|
22
|
+
"contract",
|
|
23
|
+
"specification",
|
|
24
|
+
"typescript",
|
|
25
|
+
"client-generation",
|
|
26
|
+
"openapi"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/abapify/adt-cli.git",
|
|
32
|
+
"directory": "packages/speci"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/abapify/adt-cli/tree/main/packages/speci#readme",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/abapify/adt-cli/issues"
|
|
37
|
+
}
|
|
38
|
+
}
|