@mpgd/adapter-devvit 0.11.0 → 0.12.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/README.md +32 -0
- package/dist/catalog-products.d.ts +3 -4
- package/dist/catalog-products.js +3 -4
- package/dist/commerce-provider.d.ts +1 -1
- package/dist/index.d.ts +10 -8
- package/dist/index.js +29 -33
- package/dist/payments.d.ts +7 -9
- package/dist/payments.js +0 -61
- package/dist/web.d.ts +2 -2
- package/package.json +5 -5
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @mpgd/adapter-devvit
|
|
2
|
+
|
|
3
|
+
Reddit Devvit Web platform adapter for mpgd games.
|
|
4
|
+
|
|
5
|
+
| Entry point | Purpose |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| `@mpgd/adapter-devvit` | `createDevvitPlatformGateway`, the oRPC bridge and sandbox bridge, the commerce provider and catalog helpers |
|
|
8
|
+
| `@mpgd/adapter-devvit/payments` | `normalizeDevvitCommerceProducts` and the checkout types |
|
|
9
|
+
| `@mpgd/adapter-devvit/server` | `createDevvitCommerceV2BridgeHandler`, payment-order normalizers, durable post operations, Redis stores |
|
|
10
|
+
| `@mpgd/adapter-devvit/web` | Webview helpers, including `createDevvitWebPaymentsClient()` over `purchase()` from `@devvit/web/client` |
|
|
11
|
+
| `@mpgd/adapter-devvit/surface`, `/view-mode` | Inline and expanded view-mode helpers |
|
|
12
|
+
|
|
13
|
+
## Commerce
|
|
14
|
+
|
|
15
|
+
The gateway has at most one commerce surface, `gateway.commerce`, a commerce v2 `CommerceProvider`
|
|
16
|
+
(`createDevvitCommerceProvider`). `createDevvitPlatformGateway({ commerce: { products, client } })`
|
|
17
|
+
installs it and reports its block as `getCapabilities().commerce`; without the option the gateway
|
|
18
|
+
sells nothing, has no `commerce`, and reports `createUnsupportedCommerceCapabilities()`. Reddit
|
|
19
|
+
settles a purchase on the server, so only the backend ledger the fulfillment handler writes grants
|
|
20
|
+
anything. Games purchase through `@mpgd/game-services`; see the
|
|
21
|
+
[Devvit commerce guide](../../docs/DEVVIT_COMMERCE.md).
|
|
22
|
+
|
|
23
|
+
## Migrating from v1
|
|
24
|
+
|
|
25
|
+
| Removed | Replacement |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `createDevvitCommerceAdapter`, `withDevvitCommerceAdapter`, `CreateDevvitCommerceAdapterInput` | `createDevvitPlatformGateway({ commerce })` or `createDevvitCommerceProvider` |
|
|
28
|
+
| v1 `gateway.commerce` (`getProducts`, `purchase`, `restore`, `getEntitlements`) | `gateway.commerce`, the provider (absent when nothing is sold) |
|
|
29
|
+
| `gateway.commerceV2` | `gateway.commerce` |
|
|
30
|
+
| `DevvitPaymentsClient.getEntitlements()` | The backend ledger (`listActiveEntitlementTransactions` on the server); the client is only `purchase(sku)` |
|
|
31
|
+
| `capabilities.nativeIap`, `capabilities.subscriptionIap` | `capabilities.commerce` |
|
|
32
|
+
| Bridge methods `commerce.getProducts`, `commerce.purchase`, `commerce.restore`, `commerce.getEntitlements` | `UNSUPPORTED_METHOD`; the six `commerce.v2.*` methods |
|
|
@@ -41,9 +41,8 @@ export interface CreateDevvitCommerceProductsOptions {
|
|
|
41
41
|
*/
|
|
42
42
|
export declare function createDevvitCommerceBridgeProducts(catalog: DevvitCatalogLike, target?: string): readonly DevvitCatalogBridgeProduct[];
|
|
43
43
|
/**
|
|
44
|
-
* The webview products for `createDevvitPlatformGateway({ commerce: { products } })
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* every SKU.
|
|
44
|
+
* The webview products for `createDevvitPlatformGateway({ commerce: { products } })`,
|
|
45
|
+
* derived from the same catalog entries as `createDevvitCommerceBridgeProducts`,
|
|
46
|
+
* so the webview and the server agree on every SKU.
|
|
48
47
|
*/
|
|
49
48
|
export declare function createDevvitCommerceProducts(catalog: DevvitCatalogLike, options?: CreateDevvitCommerceProductsOptions): readonly DevvitCommerceProduct[];
|
package/dist/catalog-products.js
CHANGED
|
@@ -32,10 +32,9 @@ export function createDevvitCommerceBridgeProducts(catalog, target = defaultDevv
|
|
|
32
32
|
return Object.freeze(products);
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
* The webview products for `createDevvitPlatformGateway({ commerce: { products } })
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* every SKU.
|
|
35
|
+
* The webview products for `createDevvitPlatformGateway({ commerce: { products } })`,
|
|
36
|
+
* derived from the same catalog entries as `createDevvitCommerceBridgeProducts`,
|
|
37
|
+
* so the webview and the server agree on every SKU.
|
|
39
38
|
*/
|
|
40
39
|
export function createDevvitCommerceProducts(catalog, options = {}) {
|
|
41
40
|
const target = options.target ?? defaultDevvitCatalogTarget;
|
|
@@ -40,7 +40,7 @@ export type DevvitOrdersChangeListener = (change?: DevvitOrdersChange) => void;
|
|
|
40
40
|
export interface CreateDevvitCommerceProviderInput {
|
|
41
41
|
readonly products: readonly DevvitCommerceProduct[];
|
|
42
42
|
/** Opens the Reddit checkout (`purchase()` from `@devvit/payments/client`). */
|
|
43
|
-
readonly client:
|
|
43
|
+
readonly client: DevvitPaymentsClient;
|
|
44
44
|
readonly orders: DevvitOrderSource;
|
|
45
45
|
/** Defaults to `devvit`; conformance vectors name their fixture store. */
|
|
46
46
|
readonly store?: StoreId;
|
package/dist/index.d.ts
CHANGED
|
@@ -36,15 +36,17 @@ export interface DevvitPlatformGatewayOptions {
|
|
|
36
36
|
readonly fallbackBridge?: DevvitBridge;
|
|
37
37
|
readonly rpcEndpoint?: BridgeRpcEndpoint;
|
|
38
38
|
/**
|
|
39
|
-
* Installs the commerce
|
|
40
|
-
* `
|
|
41
|
-
* `
|
|
42
|
-
*
|
|
39
|
+
* Installs the commerce provider (`createDevvitCommerceProvider`) as the
|
|
40
|
+
* gateway's `commerce`, and its capability block as the snapshot's
|
|
41
|
+
* `commerce`. Absent, the gateway sells nothing: it has no `commerce` and
|
|
42
|
+
* reports `createUnsupportedCommerceCapabilities()`, whatever the server
|
|
43
|
+
* snapshot says.
|
|
43
44
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* A purchase is granted by the backend ledger only: the fulfillment handler
|
|
46
|
+
* writes the ledger entry, and the backend's `devvit.order.v1` lookup
|
|
47
|
+
* verifier answers the reconciler's verify request from it. Owned
|
|
48
|
+
* entitlements come from that ledger through `@mpgd/game-services`, not
|
|
49
|
+
* from the payments client.
|
|
48
50
|
*/
|
|
49
51
|
readonly commerce?: DevvitGatewayCommerceOptions;
|
|
50
52
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { bridgeStorageLoadProtocol, commerceV2UnavailableBridgeErrorCode, createBridgeError, createCommerceV2UnavailableBridgeError, decodeBridgeStorageLoadData, } from '@mpgd/bridge';
|
|
1
|
+
import { bridgeStorageLoadProtocol, commerceV2UnavailableBridgeErrorCode, createBridgeError, createCommerceV2UnavailableBridgeError, createNotificationsV2UnavailableBridgeError, decodeBridgeStorageLoadData, isBridgeNotificationsV2Method, } from '@mpgd/bridge';
|
|
2
2
|
import { createBridgeOrpcClient, defaultBridgeRpcEndpoint, } from '@mpgd/bridge/orpc';
|
|
3
|
-
import { PlatformOperationError, } from '@mpgd/platform';
|
|
4
|
-
import { assertCommerceBridgeGetAvailablePurchasesResult, assertCommerceBridgeGetCapabilitiesResult, CommerceError, commerceProtocol,
|
|
3
|
+
import { createUnsupportedCommerceCapabilities, PlatformOperationError, } from '@mpgd/platform';
|
|
4
|
+
import { assertCommerceBridgeGetAvailablePurchasesResult, assertCommerceBridgeGetCapabilitiesResult, CommerceError, commerceProtocol, isCommerceErrorCode, } from '@mpgd/platform/commerce';
|
|
5
|
+
import { withShareIntentCheck } from '@mpgd/platform/routes';
|
|
5
6
|
import { assertDevvitOrderEvidence } from './commerce-contract.js';
|
|
6
7
|
import { createDevvitCommerceProvider, } from './commerce-provider.js';
|
|
7
8
|
export * from './commerce-contract.js';
|
|
@@ -45,7 +46,7 @@ export function createDevvitPlatformGateway(input) {
|
|
|
45
46
|
}
|
|
46
47
|
return response.data;
|
|
47
48
|
}
|
|
48
|
-
const
|
|
49
|
+
const commerce = input.commerce === undefined
|
|
49
50
|
? undefined
|
|
50
51
|
: createDevvitCommerceProvider({
|
|
51
52
|
...input.commerce,
|
|
@@ -54,14 +55,20 @@ export function createDevvitPlatformGateway(input) {
|
|
|
54
55
|
return {
|
|
55
56
|
target: 'reddit',
|
|
56
57
|
async getCapabilities() {
|
|
57
|
-
const capabilities =
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
const { nativeIap: _nativeIap, subscriptionIap: _subscriptionIap, commerce: _serverCommerce, ...capabilities } = await request('runtime.getCapabilities', {});
|
|
59
|
+
const availability = capabilities.providerAvailability;
|
|
60
|
+
if (typeof availability === 'object' && availability !== null && !Array.isArray(availability)) {
|
|
61
|
+
const { nativeIap: _retiredNativeIap, subscriptionIap: _retiredSubscriptionIap, ...current } = availability;
|
|
62
|
+
capabilities.providerAvailability = current;
|
|
60
63
|
}
|
|
61
64
|
// The webview provider owns the block: the server snapshot stays honest
|
|
62
|
-
// about the server, and
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
// about the server, and a gateway without a provider sells nothing.
|
|
66
|
+
return {
|
|
67
|
+
...capabilities,
|
|
68
|
+
commerce: commerce === undefined
|
|
69
|
+
? createUnsupportedCommerceCapabilities()
|
|
70
|
+
: await commerce.getCapabilities(),
|
|
71
|
+
};
|
|
65
72
|
},
|
|
66
73
|
identity: {
|
|
67
74
|
getPlayer: () => request('identity.getPlayer', {}),
|
|
@@ -72,21 +79,17 @@ export function createDevvitPlatformGateway(input) {
|
|
|
72
79
|
getLaunchIntent: () => request('presentation.getLaunchIntent', {}),
|
|
73
80
|
requestGameSurface: (payload) => request('presentation.requestGameSurface', payload),
|
|
74
81
|
},
|
|
75
|
-
|
|
82
|
+
// The bridge's host may build a share URL from the link, so it only ever
|
|
83
|
+
// sees an intent the ShareIntent contract accepts, with the link normalized.
|
|
84
|
+
sharing: withShareIntentCheck({
|
|
76
85
|
share: (payload) => request('share.share', payload),
|
|
77
86
|
readInboundShare: () => request('share.readInboundShare', {}),
|
|
78
|
-
},
|
|
87
|
+
}),
|
|
79
88
|
notifications: {
|
|
80
89
|
getStatus: (topic) => request('notifications.getStatus', { topic }),
|
|
81
90
|
requestSubscription: (topic) => request('notifications.requestSubscription', { topic }),
|
|
82
91
|
},
|
|
83
|
-
commerce: {
|
|
84
|
-
getProducts: () => request('commerce.getProducts', {}),
|
|
85
|
-
purchase: (payload) => request('commerce.purchase', payload),
|
|
86
|
-
restore: () => request('commerce.restore', {}),
|
|
87
|
-
getEntitlements: () => request('commerce.getEntitlements', {}),
|
|
88
|
-
},
|
|
89
|
-
...(commerceV2 === undefined ? {} : { commerceV2 }),
|
|
92
|
+
...(commerce === undefined ? {} : { commerce }),
|
|
90
93
|
ads: {
|
|
91
94
|
preload: (payload) => request('ads.preload', payload),
|
|
92
95
|
showRewarded: (payload) => request('ads.showRewarded', payload),
|
|
@@ -287,10 +290,14 @@ export function createDevvitSandboxBridge() {
|
|
|
287
290
|
const storage = new Map();
|
|
288
291
|
return {
|
|
289
292
|
async request(input) {
|
|
293
|
+
// Notifications v2 arrives with the Devvit provider (P3); until then
|
|
294
|
+
// every v2 method answers the dedicated code.
|
|
295
|
+
if (isBridgeNotificationsV2Method(input.method)) {
|
|
296
|
+
return createNotificationsV2UnavailableBridgeError(input, 'Devvit sandbox bridge');
|
|
297
|
+
}
|
|
290
298
|
switch (input.method) {
|
|
291
299
|
case 'runtime.getCapabilities':
|
|
292
300
|
return ok(input, {
|
|
293
|
-
nativeIap: false,
|
|
294
301
|
nativeAds: false,
|
|
295
302
|
rewardedAds: false,
|
|
296
303
|
interstitialAds: false,
|
|
@@ -301,6 +308,7 @@ export function createDevvitSandboxBridge() {
|
|
|
301
308
|
socialShare: false,
|
|
302
309
|
haptics: false,
|
|
303
310
|
localizedContent: true,
|
|
311
|
+
commerce: createUnsupportedCommerceCapabilities(),
|
|
304
312
|
});
|
|
305
313
|
case 'identity.getPlayer':
|
|
306
314
|
return ok(input, {
|
|
@@ -334,18 +342,6 @@ export function createDevvitSandboxBridge() {
|
|
|
334
342
|
return ok(input, 'approval-required');
|
|
335
343
|
case 'notifications.requestSubscription':
|
|
336
344
|
return ok(input, 'unavailable');
|
|
337
|
-
case 'commerce.getProducts':
|
|
338
|
-
case 'commerce.getEntitlements':
|
|
339
|
-
return ok(input, []);
|
|
340
|
-
case 'commerce.purchase':
|
|
341
|
-
return ok(input, {
|
|
342
|
-
status: 'cancelled',
|
|
343
|
-
entitlementIds: [],
|
|
344
|
-
});
|
|
345
|
-
case 'commerce.restore':
|
|
346
|
-
return ok(input, {
|
|
347
|
-
restoredEntitlements: [],
|
|
348
|
-
});
|
|
349
345
|
case 'ads.preload':
|
|
350
346
|
return ok(input, {});
|
|
351
347
|
case 'leaderboard.open':
|
|
@@ -385,7 +381,7 @@ export function createDevvitSandboxBridge() {
|
|
|
385
381
|
saved: key !== undefined,
|
|
386
382
|
});
|
|
387
383
|
}
|
|
388
|
-
//
|
|
384
|
+
// The sandbox sells nothing: every commerce method fails closed.
|
|
389
385
|
case 'commerce.v2.getCapabilities':
|
|
390
386
|
case 'commerce.v2.fetchProducts':
|
|
391
387
|
case 'commerce.v2.requestPurchase':
|
package/dist/payments.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PlatformEvidenceEnvelope, ProductInfo, PurchaseResult } from '@mpgd/platform';
|
|
2
2
|
/**
|
|
3
3
|
* Why a Devvit checkout ended without an order (`OrderResultEvent` with
|
|
4
4
|
* `STATUS_ERROR` or `STATUS_CANCELLED`). `code` is a commerce v2 error code
|
|
@@ -19,20 +19,18 @@ export interface DevvitCheckoutResult {
|
|
|
19
19
|
/** Read by the commerce v2 provider for a `cancelled` or `failed` checkout. */
|
|
20
20
|
readonly error?: DevvitCheckoutFailure;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* The checkout half of the Reddit payments client: the commerce v2 provider
|
|
24
|
+
* opens the checkout through it and needs nothing else. Owned entitlements are
|
|
25
|
+
* not read from the client; the backend ledger answers them through
|
|
26
|
+
* `@mpgd/game-services`, and the paid orders come from the server bridge.
|
|
27
|
+
*/
|
|
22
28
|
export interface DevvitPaymentsClient {
|
|
23
29
|
purchase(sku: string): Promise<DevvitCheckoutResult>;
|
|
24
|
-
getEntitlements(): Promise<readonly Entitlement[]>;
|
|
25
30
|
}
|
|
26
31
|
export interface DevvitCommerceProduct {
|
|
27
32
|
readonly info: ProductInfo;
|
|
28
33
|
readonly sku: string;
|
|
29
34
|
}
|
|
30
|
-
export interface CreateDevvitCommerceAdapterInput {
|
|
31
|
-
readonly products: readonly DevvitCommerceProduct[];
|
|
32
|
-
readonly client: DevvitPaymentsClient;
|
|
33
|
-
readonly onCheckoutError?: (error: unknown) => void;
|
|
34
|
-
}
|
|
35
|
-
export declare function createDevvitCommerceAdapter(input: CreateDevvitCommerceAdapterInput): CommerceAdapter;
|
|
36
|
-
export declare function withDevvitCommerceAdapter(gateway: PlatformGateway, commerce: CommerceAdapter): PlatformGateway;
|
|
37
35
|
/** Frozen copies of the configured products; duplicate ids or SKUs and blank SKUs are rejected. */
|
|
38
36
|
export declare function normalizeDevvitCommerceProducts(input: readonly DevvitCommerceProduct[]): readonly DevvitCommerceProduct[];
|
package/dist/payments.js
CHANGED
|
@@ -1,58 +1,3 @@
|
|
|
1
|
-
export function createDevvitCommerceAdapter(input) {
|
|
2
|
-
const products = normalizeDevvitCommerceProducts(input.products);
|
|
3
|
-
const productsById = new Map(products.map((product) => [product.info.id, product]));
|
|
4
|
-
return {
|
|
5
|
-
async getProducts() {
|
|
6
|
-
return products.map((product) => product.info);
|
|
7
|
-
},
|
|
8
|
-
async purchase(request) {
|
|
9
|
-
const product = productsById.get(request.productId);
|
|
10
|
-
if (product === undefined) {
|
|
11
|
-
return failedPurchase();
|
|
12
|
-
}
|
|
13
|
-
try {
|
|
14
|
-
// Reddit's platform order ID is authoritative. Do not attach client operation
|
|
15
|
-
// metadata whose identifiers can exceed Devvit's metadata constraints.
|
|
16
|
-
const result = await input.client.purchase(product.sku);
|
|
17
|
-
return {
|
|
18
|
-
status: result.status,
|
|
19
|
-
...(result.orderId === undefined ? {} : { transactionId: result.orderId }),
|
|
20
|
-
// Devvit server fulfillment and a subsequent entitlement read are authoritative.
|
|
21
|
-
entitlementIds: [],
|
|
22
|
-
...(result.evidence === undefined ? {} : { evidence: result.evidence }),
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
input.onCheckoutError?.(error);
|
|
27
|
-
return failedPurchase();
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
async restore() {
|
|
31
|
-
return { restoredEntitlements: await input.client.getEntitlements() };
|
|
32
|
-
},
|
|
33
|
-
async getEntitlements() {
|
|
34
|
-
return input.client.getEntitlements();
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
export function withDevvitCommerceAdapter(gateway, commerce) {
|
|
39
|
-
if (gateway.target !== 'reddit') {
|
|
40
|
-
throw new TypeError('Devvit commerce can only be installed on a reddit gateway.');
|
|
41
|
-
}
|
|
42
|
-
if ('getTargetRuntime' in gateway && typeof gateway.getTargetRuntime === 'function') {
|
|
43
|
-
throw new TypeError('Devvit commerce must be installed before target availability is applied.');
|
|
44
|
-
}
|
|
45
|
-
return {
|
|
46
|
-
...gateway,
|
|
47
|
-
async getCapabilities() {
|
|
48
|
-
return {
|
|
49
|
-
...await gateway.getCapabilities(),
|
|
50
|
-
nativeIap: true,
|
|
51
|
-
};
|
|
52
|
-
},
|
|
53
|
-
commerce,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
1
|
/** Frozen copies of the configured products; duplicate ids or SKUs and blank SKUs are rejected. */
|
|
57
2
|
export function normalizeDevvitCommerceProducts(input) {
|
|
58
3
|
const productIds = new Set();
|
|
@@ -75,9 +20,3 @@ export function normalizeDevvitCommerceProducts(input) {
|
|
|
75
20
|
});
|
|
76
21
|
}));
|
|
77
22
|
}
|
|
78
|
-
function failedPurchase() {
|
|
79
|
-
return Object.freeze({
|
|
80
|
-
status: 'failed',
|
|
81
|
-
entitlementIds: [],
|
|
82
|
-
});
|
|
83
|
-
}
|
package/dist/web.d.ts
CHANGED
|
@@ -39,8 +39,8 @@ export interface DevvitOrderResultEventLike {
|
|
|
39
39
|
readonly errorCode?: number | undefined;
|
|
40
40
|
readonly errorMessage?: string | undefined;
|
|
41
41
|
}
|
|
42
|
-
/** The checkout
|
|
43
|
-
export type DevvitWebPaymentsClient =
|
|
42
|
+
/** The checkout client `createDevvitWebPaymentsClient` returns; the v2 provider needs nothing else. */
|
|
43
|
+
export type DevvitWebPaymentsClient = DevvitPaymentsClient;
|
|
44
44
|
/**
|
|
45
45
|
* Maps one Reddit checkout answer to the adapter's `DevvitCheckoutResult`:
|
|
46
46
|
* `STATUS_SUCCESS` with an order id is `completed` (the order id is Reddit's;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/adapter-devvit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Reddit Devvit Web platform adapter for mpgd games.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -55,13 +55,13 @@
|
|
|
55
55
|
],
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@devvit/web": "0.14.1",
|
|
58
|
-
"@mpgd/
|
|
59
|
-
"@mpgd/
|
|
60
|
-
"@mpgd/
|
|
58
|
+
"@mpgd/bridge": "0.13.1",
|
|
59
|
+
"@mpgd/platform": "0.23.1",
|
|
60
|
+
"@mpgd/game-services": "0.24.1"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/node": "^24.0.0",
|
|
64
|
-
"ttsc": "0.
|
|
64
|
+
"ttsc": "0.32.1",
|
|
65
65
|
"typescript": "7.0.2",
|
|
66
66
|
"vitest": "^4.0.0"
|
|
67
67
|
},
|