@contractkit/plugin-typescript 0.20.0 → 0.21.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.
- package/.turbo/turbo-build$colon$ci.log +6 -6
- package/.turbo/turbo-test$colon$ci.log +15 -15
- package/CHANGELOG.md +12 -0
- package/coverage/clover.xml +418 -384
- package/coverage/coverage-final.json +3 -3
- package/coverage/index.html +19 -19
- package/coverage/src/codegen-contract.ts.html +1 -1
- package/coverage/src/codegen-operation.ts.html +1 -1
- package/coverage/src/codegen-plain-types.ts.html +1 -1
- package/coverage/src/codegen-sdk.ts.html +291 -180
- package/coverage/src/index.html +38 -38
- package/coverage/src/index.ts.html +249 -78
- package/coverage/src/path-utils.ts.html +109 -13
- package/coverage/src/ts-render.ts.html +1 -1
- package/coverage/tests/helpers.ts.html +1 -1
- package/coverage/tests/index.html +1 -1
- package/dist/codegen-sdk.d.ts +40 -15
- package/dist/codegen-sdk.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +175 -94
- package/dist/index.js.map +1 -1
- package/dist/path-utils.d.ts +11 -0
- package/dist/path-utils.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/codegen-sdk.ts +133 -96
- package/src/index.ts +97 -40
- package/src/path-utils.ts +32 -0
- package/tests/codegen-sdk.test.ts +73 -78
- package/tests/codegen-server.test.ts +28 -16
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
generateSdk,
|
|
4
4
|
generateSdkOptions,
|
|
5
5
|
generateSdkAggregator,
|
|
6
|
+
generateAreaClient,
|
|
6
7
|
deriveClientClassName,
|
|
7
8
|
deriveClientPropertyName,
|
|
8
9
|
deriveAreaClientClassName,
|
|
@@ -1536,60 +1537,83 @@ describe('area + subarea name derivation', () => {
|
|
|
1536
1537
|
// ─── generateSdkAggregator — area + subarea wiring ────────────────────────
|
|
1537
1538
|
|
|
1538
1539
|
describe('generateSdkAggregator — area + subarea', () => {
|
|
1539
|
-
it('
|
|
1540
|
+
it('imports each area client and exposes it on Sdk', () => {
|
|
1540
1541
|
const out = generateSdkAggregator({
|
|
1541
1542
|
topLevelClients: [],
|
|
1542
1543
|
areas: [
|
|
1543
1544
|
{
|
|
1544
1545
|
area: 'identity',
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1546
|
+
client: { className: 'IdentityClient', propertyName: 'identity', importPath: './identity/identity.client.js' },
|
|
1547
|
+
},
|
|
1548
|
+
],
|
|
1549
|
+
});
|
|
1550
|
+
expect(out).toContain("import { IdentityClient } from './identity/identity.client.js'");
|
|
1551
|
+
expect(out).toContain('export class Sdk {');
|
|
1552
|
+
expect(out).toContain('readonly identity: IdentityClient');
|
|
1553
|
+
expect(out).toContain('this.identity = new IdentityClient(sdkFetch)');
|
|
1554
|
+
// The aggregator no longer declares the IdentityClient itself.
|
|
1555
|
+
expect(out).not.toContain('class IdentityClient {');
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
it('mixes area clients with top-level (no-area) clients', () => {
|
|
1559
|
+
const out = generateSdkAggregator({
|
|
1560
|
+
topLevelClients: [{ className: 'WebhooksClient', propertyName: 'webhooks', importPath: './webhooks.client.js' }],
|
|
1561
|
+
areas: [
|
|
1562
|
+
{
|
|
1563
|
+
area: 'payments',
|
|
1564
|
+
client: { className: 'PaymentsClient', propertyName: 'payments', importPath: './payments/payments.client.js' },
|
|
1556
1565
|
},
|
|
1557
1566
|
],
|
|
1558
1567
|
});
|
|
1559
|
-
expect(out).toContain(
|
|
1560
|
-
expect(out).toContain(
|
|
1561
|
-
expect(out).toContain('
|
|
1568
|
+
expect(out).toContain('readonly payments: PaymentsClient');
|
|
1569
|
+
expect(out).toContain('readonly webhooks: WebhooksClient');
|
|
1570
|
+
expect(out).toContain('this.payments = new PaymentsClient(sdkFetch)');
|
|
1571
|
+
expect(out).toContain('this.webhooks = new WebhooksClient(sdkFetch)');
|
|
1572
|
+
});
|
|
1573
|
+
});
|
|
1574
|
+
|
|
1575
|
+
describe('generateAreaClient — <Area>Client emission', () => {
|
|
1576
|
+
it('emits one <Area>Client class with subarea property wiring', () => {
|
|
1577
|
+
const out = generateAreaClient({
|
|
1578
|
+
area: 'identity',
|
|
1579
|
+
outPath: '/out/identity/identity.client.ts',
|
|
1580
|
+
inlineFiles: [],
|
|
1581
|
+
subareaClients: [
|
|
1582
|
+
{
|
|
1583
|
+
propertyName: 'invitations',
|
|
1584
|
+
client: { className: 'IdentityInvitationsClient', propertyName: 'invitations', importPath: './invitations.client.js' },
|
|
1585
|
+
},
|
|
1586
|
+
{
|
|
1587
|
+
propertyName: 'users',
|
|
1588
|
+
client: { className: 'IdentityUsersClient', propertyName: 'users', importPath: './users.client.js' },
|
|
1589
|
+
},
|
|
1590
|
+
],
|
|
1591
|
+
sdkOptionsPath: '/out/sdk-options.ts',
|
|
1592
|
+
});
|
|
1593
|
+
expect(out).toContain("import { IdentityInvitationsClient } from './invitations.client.js'");
|
|
1594
|
+
expect(out).toContain("import { IdentityUsersClient } from './users.client.js'");
|
|
1595
|
+
expect(out).toContain('export class IdentityClient {');
|
|
1562
1596
|
expect(out).toContain('readonly invitations: IdentityInvitationsClient');
|
|
1563
1597
|
expect(out).toContain('readonly users: IdentityUsersClient');
|
|
1564
1598
|
expect(out).toContain('this.invitations = new IdentityInvitationsClient(fetch)');
|
|
1565
1599
|
expect(out).toContain('this.users = new IdentityUsersClient(fetch)');
|
|
1566
|
-
// Sdk wires the area property
|
|
1567
|
-
expect(out).toContain('export class Sdk {');
|
|
1568
|
-
expect(out).toContain('readonly identity: IdentityClient');
|
|
1569
|
-
expect(out).toContain('this.identity = new IdentityClient(sdkFetch)');
|
|
1570
1600
|
});
|
|
1571
1601
|
|
|
1572
|
-
it('inlines methods from area-level (no-subarea) files into
|
|
1602
|
+
it('inlines methods from area-level (no-subarea) files into the area client', () => {
|
|
1573
1603
|
const root = opRoot(
|
|
1574
1604
|
[opRoute('/me', [opOperation('get', { sdk: 'getCurrentUser', responses: [opResponse(200, 'User', 'application/json')] })])],
|
|
1575
1605
|
'identity.ck',
|
|
1576
1606
|
{ area: 'identity' },
|
|
1577
1607
|
);
|
|
1578
|
-
const out =
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
subareaClients: [],
|
|
1585
|
-
},
|
|
1586
|
-
],
|
|
1608
|
+
const out = generateAreaClient({
|
|
1609
|
+
area: 'identity',
|
|
1610
|
+
outPath: '/out/identity/identity.client.ts',
|
|
1611
|
+
inlineFiles: [{ root, codegenOptions: { outPath: '/out/identity/identity.client.ts', sdkOptionsPath: '/out/sdk-options.ts' } }],
|
|
1612
|
+
subareaClients: [],
|
|
1613
|
+
sdkOptionsPath: '/out/sdk-options.ts',
|
|
1587
1614
|
});
|
|
1588
|
-
expect(out).toContain('class IdentityClient {');
|
|
1615
|
+
expect(out).toContain('export class IdentityClient {');
|
|
1589
1616
|
expect(out).toContain('async getCurrentUser(');
|
|
1590
|
-
// No subarea property declarations on this client
|
|
1591
|
-
expect(out).not.toMatch(/readonly \w+: \w+;[\s\S]*async getCurrentUser/);
|
|
1592
|
-
// The fetch arg is captured as `private fetch` since methods reference `this.fetch`
|
|
1593
1617
|
expect(out).toContain('constructor(private fetch: SdkFetch)');
|
|
1594
1618
|
});
|
|
1595
1619
|
|
|
@@ -1599,20 +1623,17 @@ describe('generateSdkAggregator — area + subarea', () => {
|
|
|
1599
1623
|
'identity.ck',
|
|
1600
1624
|
{ area: 'identity' },
|
|
1601
1625
|
);
|
|
1602
|
-
const out =
|
|
1603
|
-
|
|
1604
|
-
|
|
1626
|
+
const out = generateAreaClient({
|
|
1627
|
+
area: 'identity',
|
|
1628
|
+
outPath: '/out/identity/identity.client.ts',
|
|
1629
|
+
inlineFiles: [{ root: inlineRoot, codegenOptions: { outPath: '/out/identity/identity.client.ts', sdkOptionsPath: '/out/sdk-options.ts' } }],
|
|
1630
|
+
subareaClients: [
|
|
1605
1631
|
{
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
subareaClients: [
|
|
1609
|
-
{
|
|
1610
|
-
propertyName: 'invitations',
|
|
1611
|
-
client: { className: 'IdentityInvitationsClient', propertyName: 'invitations', importPath: './identity/invitations.client.js' },
|
|
1612
|
-
},
|
|
1613
|
-
],
|
|
1632
|
+
propertyName: 'invitations',
|
|
1633
|
+
client: { className: 'IdentityInvitationsClient', propertyName: 'invitations', importPath: './invitations.client.js' },
|
|
1614
1634
|
},
|
|
1615
1635
|
],
|
|
1636
|
+
sdkOptionsPath: '/out/sdk-options.ts',
|
|
1616
1637
|
});
|
|
1617
1638
|
expect(out).toContain('readonly invitations: IdentityInvitationsClient');
|
|
1618
1639
|
expect(out).toContain('async getCurrentUser(');
|
|
@@ -1631,42 +1652,16 @@ describe('generateSdkAggregator — area + subarea', () => {
|
|
|
1631
1652
|
{ area: 'identity' },
|
|
1632
1653
|
);
|
|
1633
1654
|
expect(() =>
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
{ root: a, codegenOptions: { outPath: '/out/sdk.ts', sdkOptionsPath: '/out/sdk-options.ts' } },
|
|
1641
|
-
{ root: b, codegenOptions: { outPath: '/out/sdk.ts', sdkOptionsPath: '/out/sdk-options.ts' } },
|
|
1642
|
-
],
|
|
1643
|
-
subareaClients: [],
|
|
1644
|
-
},
|
|
1655
|
+
generateAreaClient({
|
|
1656
|
+
area: 'identity',
|
|
1657
|
+
outPath: '/out/identity/identity.client.ts',
|
|
1658
|
+
inlineFiles: [
|
|
1659
|
+
{ root: a, codegenOptions: { outPath: '/out/identity/identity.client.ts', sdkOptionsPath: '/out/sdk-options.ts' } },
|
|
1660
|
+
{ root: b, codegenOptions: { outPath: '/out/identity/identity.client.ts', sdkOptionsPath: '/out/sdk-options.ts' } },
|
|
1645
1661
|
],
|
|
1662
|
+
subareaClients: [],
|
|
1663
|
+
sdkOptionsPath: '/out/sdk-options.ts',
|
|
1646
1664
|
}),
|
|
1647
1665
|
).toThrow(/duplicate method 'getCurrentUser' in area 'identity'/);
|
|
1648
1666
|
});
|
|
1649
|
-
|
|
1650
|
-
it('mixes area clients with top-level (no-area) clients', () => {
|
|
1651
|
-
const out = generateSdkAggregator({
|
|
1652
|
-
topLevelClients: [{ className: 'WebhooksClient', propertyName: 'webhooks', importPath: './webhooks.client.js' }],
|
|
1653
|
-
areas: [
|
|
1654
|
-
{
|
|
1655
|
-
area: 'payments',
|
|
1656
|
-
inlineFiles: [],
|
|
1657
|
-
subareaClients: [
|
|
1658
|
-
{
|
|
1659
|
-
propertyName: 'transfers',
|
|
1660
|
-
client: { className: 'PaymentsTransfersClient', propertyName: 'transfers', importPath: './payments/transfers.client.js' },
|
|
1661
|
-
},
|
|
1662
|
-
],
|
|
1663
|
-
},
|
|
1664
|
-
],
|
|
1665
|
-
});
|
|
1666
|
-
// Sdk has both an area property AND a top-level property
|
|
1667
|
-
expect(out).toContain('readonly payments: PaymentsClient');
|
|
1668
|
-
expect(out).toContain('readonly webhooks: WebhooksClient');
|
|
1669
|
-
expect(out).toContain('this.payments = new PaymentsClient(sdkFetch)');
|
|
1670
|
-
expect(out).toContain('this.webhooks = new WebhooksClient(sdkFetch)');
|
|
1671
|
-
});
|
|
1672
1667
|
});
|
|
@@ -204,7 +204,7 @@ describe('createTypescriptPlugin (sdk) — area / subarea grouping', () => {
|
|
|
204
204
|
return undefined;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
it('emits a leaf <Area><Subarea>Client and
|
|
207
|
+
it('emits a leaf <Area><Subarea>Client and a separate <Area>Client wrapper file', async () => {
|
|
208
208
|
const plugin = createTypescriptPlugin({ sdk: { output: { sdk: 'sdk.ts', clients: '{area}/{subarea}.client.ts' } } }, '/project');
|
|
209
209
|
const ctx = makeCtx('/project');
|
|
210
210
|
const root = opRoot(
|
|
@@ -220,18 +220,25 @@ describe('createTypescriptPlugin (sdk) — area / subarea grouping', () => {
|
|
|
220
220
|
expect(leaf!).toContain('export class IdentityInvitationsClient');
|
|
221
221
|
expect(leaf!).toContain('async createInvitation');
|
|
222
222
|
|
|
223
|
-
//
|
|
223
|
+
// Separate <area>.client.ts holds the IdentityClient wrapper
|
|
224
|
+
const areaClient = findEmitted(ctx.emitted, 'identity/identity.client.ts');
|
|
225
|
+
expect(areaClient).toBeDefined();
|
|
226
|
+
expect(areaClient!).toContain('export class IdentityClient {');
|
|
227
|
+
expect(areaClient!).toContain('readonly invitations: IdentityInvitationsClient');
|
|
228
|
+
expect(areaClient!).toContain('this.invitations = new IdentityInvitationsClient(fetch)');
|
|
229
|
+
|
|
230
|
+
// sdk.ts just imports + wires the area client onto Sdk
|
|
224
231
|
const sdk = findEmitted(ctx.emitted, '/sdk.ts');
|
|
225
232
|
expect(sdk).toBeDefined();
|
|
226
|
-
expect(sdk!).toContain(
|
|
227
|
-
expect(sdk!).toContain('readonly invitations: IdentityInvitationsClient');
|
|
228
|
-
expect(sdk!).toContain('this.invitations = new IdentityInvitationsClient(fetch)');
|
|
233
|
+
expect(sdk!).toContain("import { IdentityClient } from './identity/identity.client.js'");
|
|
229
234
|
expect(sdk!).toContain('export class Sdk {');
|
|
230
235
|
expect(sdk!).toContain('readonly identity: IdentityClient');
|
|
231
236
|
expect(sdk!).toContain('this.identity = new IdentityClient(sdkFetch)');
|
|
237
|
+
// The class declaration itself is no longer in sdk.ts.
|
|
238
|
+
expect(sdk!).not.toContain('class IdentityClient {');
|
|
232
239
|
});
|
|
233
240
|
|
|
234
|
-
it('
|
|
241
|
+
it('emits a synthesized <area>.client.ts for area-level (no-subarea) inputs instead of inlining into sdk.ts', async () => {
|
|
235
242
|
const plugin = createTypescriptPlugin({ sdk: { output: { sdk: 'sdk.ts', clients: '{area}/{filename}.client.ts' } } }, '/project');
|
|
236
243
|
const ctx = makeCtx('/project');
|
|
237
244
|
const root = opRoot(
|
|
@@ -241,19 +248,24 @@ describe('createTypescriptPlugin (sdk) — area / subarea grouping', () => {
|
|
|
241
248
|
);
|
|
242
249
|
await plugin.generateTargets!(inputs([root]), ctx);
|
|
243
250
|
|
|
244
|
-
//
|
|
251
|
+
// The contributing file does NOT get its own *.client.ts (per the previous design).
|
|
245
252
|
for (const path of ctx.emitted.keys()) {
|
|
246
253
|
expect(path).not.toMatch(/identity[/-]me\.client\.ts$/);
|
|
247
254
|
}
|
|
248
255
|
|
|
249
|
-
//
|
|
256
|
+
// The area client lives in <area>/<area>.client.ts and holds the merged methods.
|
|
257
|
+
const areaClient = findEmitted(ctx.emitted, 'identity/identity.client.ts');
|
|
258
|
+
expect(areaClient).toBeDefined();
|
|
259
|
+
expect(areaClient!).toContain('export class IdentityClient {');
|
|
260
|
+
expect(areaClient!).toContain('async getCurrentUser');
|
|
261
|
+
|
|
262
|
+
// sdk.ts just imports it.
|
|
250
263
|
const sdk = findEmitted(ctx.emitted, '/sdk.ts');
|
|
251
|
-
expect(sdk).
|
|
252
|
-
expect(sdk!).toContain('class IdentityClient {');
|
|
253
|
-
expect(sdk!).toContain('async getCurrentUser');
|
|
264
|
+
expect(sdk!).toContain("import { IdentityClient } from './identity/identity.client.js'");
|
|
265
|
+
expect(sdk!).not.toContain('class IdentityClient {');
|
|
254
266
|
});
|
|
255
267
|
|
|
256
|
-
it('mixes area-level inline methods with subarea property wiring on the same area
|
|
268
|
+
it('mixes area-level inline methods with subarea property wiring on the same <area>.client.ts', async () => {
|
|
257
269
|
const plugin = createTypescriptPlugin({ sdk: { output: { sdk: 'sdk.ts', clients: '{area}/{filename}.client.ts' } } }, '/project');
|
|
258
270
|
const ctx = makeCtx('/project');
|
|
259
271
|
const me = opRoot(
|
|
@@ -268,10 +280,10 @@ describe('createTypescriptPlugin (sdk) — area / subarea grouping', () => {
|
|
|
268
280
|
);
|
|
269
281
|
await plugin.generateTargets!(inputs([me, invitations]), ctx);
|
|
270
282
|
|
|
271
|
-
const
|
|
272
|
-
expect(
|
|
273
|
-
expect(
|
|
274
|
-
expect(
|
|
283
|
+
const areaClient = findEmitted(ctx.emitted, 'identity/identity.client.ts')!;
|
|
284
|
+
expect(areaClient).toContain('export class IdentityClient {');
|
|
285
|
+
expect(areaClient).toContain('readonly invitations: IdentityInvitationsClient');
|
|
286
|
+
expect(areaClient).toContain('async getCurrentUser');
|
|
275
287
|
});
|
|
276
288
|
|
|
277
289
|
it('preserves legacy flat wiring for files with no area', async () => {
|