@kizunajs/kotlin 2.0.0-beta.19 → 2.0.0-beta.20

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/dist/cli.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const require_generator = require("./generator-D4SnsG2Q.cjs");
2
+ const require_generator = require("./generator-Bg4HlRb3.cjs");
3
3
  let node_fs = require("node:fs");
4
4
  let node_path = require("node:path");
5
5
  let node_util = require("node:util");
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as generateKotlinClient } from "./generator-DT7c2Vo3.mjs";
2
+ import { t as generateKotlinClient } from "./generator-D4GvH1-a.mjs";
3
3
  import { mkdirSync, writeFileSync } from "node:fs";
4
4
  import { dirname, resolve } from "node:path";
5
5
  import { parseArgs } from "node:util";
@@ -1393,7 +1393,7 @@ const emitMethodBody = (writer, method, context) => {
1393
1393
  else setHeader(accessor);
1394
1394
  }
1395
1395
  writer.line("requestInterceptor?.invoke(requestBuilder)");
1396
- writer.line("val httpResponse = Kizuna.execute(client, requestBuilder.build())");
1396
+ writer.line("val httpResponse = Kizuna.send(client, requestBuilder.build(), retry)");
1397
1397
  if (method.stream) {
1398
1398
  emitStreamMethodTail(writer, method, context);
1399
1399
  return;
@@ -1545,7 +1545,7 @@ const emitSubClientMethod = (writer, method, context) => {
1545
1545
  };
1546
1546
  const emitSubClientClass = (writer, group, clientName, context) => {
1547
1547
  writer.blank();
1548
- writer.block(`class ${group.className}(private val client: OkHttpClient, private val baseUrl: String, private val json: Json, private val requestContextHeaders: Map<String, String>, private val requestInterceptor: (suspend (Request.Builder) -> Unit)?, private val responseInterceptor: (suspend (Request, Response) -> Unit)?)`, () => {
1548
+ writer.block(`class ${group.className}(private val client: OkHttpClient, private val baseUrl: String, private val json: Json, private val requestContextHeaders: Map<String, String>, private val requestInterceptor: (suspend (Request.Builder) -> Unit)?, private val responseInterceptor: (suspend (Request, Response) -> Unit)?, private val retry: ${clientName}.RetryPolicy?)`, () => {
1549
1549
  for (const method of group.methods) {
1550
1550
  writer.blank();
1551
1551
  emitSubClientMethod(writer, method, context);
@@ -1650,6 +1650,47 @@ const emitKizunaObject = (writer, options) => {
1650
1650
  writer.line("}");
1651
1651
  });
1652
1652
  writer.blank();
1653
+ writer.block(`suspend fun send(client: OkHttpClient, request: Request, retry: ${options.clientName}.RetryPolicy?): Response`, () => {
1654
+ writer.line("val idempotent = request.method in setOf(\"GET\", \"HEAD\", \"OPTIONS\", \"PUT\", \"DELETE\")");
1655
+ writer.line("val policy = retry?.takeIf { idempotent && request.body?.isOneShot() != true }");
1656
+ writer.line("var attempt = 1");
1657
+ writer.block("while (true)", () => {
1658
+ writer.line("val response = try {");
1659
+ writer.line(" execute(client, request)");
1660
+ writer.line("} catch (error: java.io.IOException) {");
1661
+ writer.line(" val delay = policy?.let { retryDelay(it, attempt, null) } ?: throw error");
1662
+ writer.line(" kotlinx.coroutines.delay(delay)");
1663
+ writer.line(" attempt += 1");
1664
+ writer.line(" continue");
1665
+ writer.line("}");
1666
+ writer.line("val delay = policy?.let { retryDelay(it, attempt, response) } ?: return response");
1667
+ writer.line("response.close()");
1668
+ writer.line("kotlinx.coroutines.delay(delay)");
1669
+ writer.line("attempt += 1");
1670
+ });
1671
+ });
1672
+ writer.blank();
1673
+ writer.block(`fun retryDelay(policy: ${options.clientName}.RetryPolicy, attempt: Int, response: Response?): Long?`, () => {
1674
+ writer.line("if (attempt >= policy.maximumAttempts) return null");
1675
+ writer.line("val ceiling = minOf(policy.maximumDelay.inWholeMilliseconds, policy.initialDelay.inWholeMilliseconds shl (attempt - 1))");
1676
+ writer.line("val backoff = (Math.random() * ceiling).toLong()");
1677
+ writer.line("if (response == null) return backoff");
1678
+ writer.line("if (response.code != 429 && response.code != 503) return null");
1679
+ writer.line("val asked = retryAfter(response.header(\"Retry-After\")) ?: return backoff");
1680
+ writer.line("return if (asked <= policy.maximumDelay.inWholeMilliseconds) asked else null");
1681
+ });
1682
+ writer.blank();
1683
+ writer.block("fun retryAfter(header: String?): Long?", () => {
1684
+ writer.line("val value = header?.trim() ?: return null");
1685
+ writer.line("value.toLongOrNull()?.let { return if (it >= 0) it * 1000 else null }");
1686
+ writer.line("val date = try {");
1687
+ writer.line(" java.time.ZonedDateTime.parse(value, java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME)");
1688
+ writer.line("} catch (_: java.time.format.DateTimeParseException) {");
1689
+ writer.line(" return null");
1690
+ writer.line("}");
1691
+ writer.line("return maxOf(0L, date.toInstant().toEpochMilli() - System.currentTimeMillis())");
1692
+ });
1693
+ writer.blank();
1653
1694
  writer.block("suspend fun execute(client: OkHttpClient, request: Request): Response", () => {
1654
1695
  writer.line("return kotlinx.coroutines.suspendCancellableCoroutine { continuation ->");
1655
1696
  writer.indent(() => {
@@ -1680,7 +1721,7 @@ const emitClient = (writer, config, partition, context, typesByOperation, regist
1680
1721
  const contextRequired = contextFields.some((field) => !hasDefault(field));
1681
1722
  const contextParam = contextFields.length > 0 ? `requestContext: RequestContext${contextRequired ? "" : " = RequestContext()"}, ` : "";
1682
1723
  writer.blank();
1683
- writer.block(`class ${clientName}(private val baseUrl: String, ${contextParam}private val client: OkHttpClient = OkHttpClient(), private val json: Json = Json { ignoreUnknownKeys = true }, private val requestInterceptor: (suspend (Request.Builder) -> Unit)? = null, private val responseInterceptor: (suspend (Request, Response) -> Unit)? = null)`, () => {
1724
+ writer.block(`class ${clientName}(private val baseUrl: String, ${contextParam}private val client: OkHttpClient = OkHttpClient(), private val json: Json = Json { ignoreUnknownKeys = true }, private val requestInterceptor: (suspend (Request.Builder) -> Unit)? = null, private val responseInterceptor: (suspend (Request, Response) -> Unit)? = null, private val retry: RetryPolicy? = null)`, () => {
1684
1725
  if (contextFields.length > 0) {
1685
1726
  writer.blank();
1686
1727
  writer.line("/** Values sent as headers on every request, from the api's request context. */");
@@ -1691,6 +1732,20 @@ const emitClient = (writer, config, partition, context, typesByOperation, regist
1691
1732
  else writer.line(`put(${stringLiteral(field.wireName)}, requestContext.${escapeKeyword(field.name)})`);
1692
1733
  });
1693
1734
  }
1735
+ writer.blank();
1736
+ writer.line("/**");
1737
+ writer.line(" * Retries a GET, HEAD, OPTIONS, PUT or DELETE that failed on the network or was answered 429 or 503,");
1738
+ writer.line(" * waiting what Retry-After asks or backing off.");
1739
+ writer.line(" *");
1740
+ writer.line(" * @property maximumAttempts Attempts in all, the first included.");
1741
+ writer.line(" * @property initialDelay What the first backoff waits at most.");
1742
+ writer.line(" * @property maximumDelay What any wait may take.");
1743
+ writer.line(" */");
1744
+ emitConstructorClass(writer, "data class RetryPolicy", [
1745
+ "val maximumAttempts: Int = 3",
1746
+ "val initialDelay: kotlin.time.Duration = 500.milliseconds",
1747
+ "val maximumDelay: kotlin.time.Duration = 30.seconds"
1748
+ ]);
1694
1749
  if (usesMultipart) {
1695
1750
  writer.blank();
1696
1751
  emitConstructorClass(writer, "data class MultipartFile", [
@@ -1746,7 +1801,7 @@ const emitClient = (writer, config, partition, context, typesByOperation, regist
1746
1801
  }
1747
1802
  if (groups.length > 0) for (const group of groups) {
1748
1803
  writer.blank();
1749
- writer.line(`val ${escapeKeyword(group.propertyName)} = ${group.className}(client, baseUrl, json, ${contextFields.length > 0 ? "requestContextHeaders" : "emptyMap()"}, requestInterceptor, responseInterceptor)`);
1804
+ writer.line(`val ${escapeKeyword(group.propertyName)} = ${group.className}(client, baseUrl, json, ${contextFields.length > 0 ? "requestContextHeaders" : "emptyMap()"}, requestInterceptor, responseInterceptor, retry)`);
1750
1805
  }
1751
1806
  for (const method of flatMethods) {
1752
1807
  writer.blank();
@@ -1791,6 +1846,8 @@ const renderKotlinClient = (api, partition, registry, config) => {
1791
1846
  writer.line("import okhttp3.*");
1792
1847
  writer.line("import okhttp3.MediaType.Companion.toMediaType");
1793
1848
  writer.line("import okhttp3.RequestBody.Companion.toRequestBody");
1849
+ writer.line("import kotlin.time.Duration.Companion.milliseconds");
1850
+ writer.line("import kotlin.time.Duration.Companion.seconds");
1794
1851
  writer.line("import okhttp3.HttpUrl.Companion.toHttpUrl");
1795
1852
  writer.blank();
1796
1853
  if (registry.all().some((type) => type.kind === "enum-class")) {
@@ -1847,7 +1904,10 @@ const renderKotlinClient = (api, partition, registry, config) => {
1847
1904
  });
1848
1905
  emitClient(writer, { clientName }, partition, context, typesByOperation, registry);
1849
1906
  for (const group of partition.groups) emitSubClientClass(writer, group, clientName, context);
1850
- emitKizunaObject(writer, { streaming: usesStreaming });
1907
+ emitKizunaObject(writer, {
1908
+ streaming: usesStreaming,
1909
+ clientName
1910
+ });
1851
1911
  for (const warning of registry.warnings()) process.stderr.write(`[kizuna/kotlin] JsonElement fallback at ${warning}\n`);
1852
1912
  return writer.toString();
1853
1913
  };
@@ -1393,7 +1393,7 @@ const emitMethodBody = (writer, method, context) => {
1393
1393
  else setHeader(accessor);
1394
1394
  }
1395
1395
  writer.line("requestInterceptor?.invoke(requestBuilder)");
1396
- writer.line("val httpResponse = Kizuna.execute(client, requestBuilder.build())");
1396
+ writer.line("val httpResponse = Kizuna.send(client, requestBuilder.build(), retry)");
1397
1397
  if (method.stream) {
1398
1398
  emitStreamMethodTail(writer, method, context);
1399
1399
  return;
@@ -1545,7 +1545,7 @@ const emitSubClientMethod = (writer, method, context) => {
1545
1545
  };
1546
1546
  const emitSubClientClass = (writer, group, clientName, context) => {
1547
1547
  writer.blank();
1548
- writer.block(`class ${group.className}(private val client: OkHttpClient, private val baseUrl: String, private val json: Json, private val requestContextHeaders: Map<String, String>, private val requestInterceptor: (suspend (Request.Builder) -> Unit)?, private val responseInterceptor: (suspend (Request, Response) -> Unit)?)`, () => {
1548
+ writer.block(`class ${group.className}(private val client: OkHttpClient, private val baseUrl: String, private val json: Json, private val requestContextHeaders: Map<String, String>, private val requestInterceptor: (suspend (Request.Builder) -> Unit)?, private val responseInterceptor: (suspend (Request, Response) -> Unit)?, private val retry: ${clientName}.RetryPolicy?)`, () => {
1549
1549
  for (const method of group.methods) {
1550
1550
  writer.blank();
1551
1551
  emitSubClientMethod(writer, method, context);
@@ -1650,6 +1650,47 @@ const emitKizunaObject = (writer, options) => {
1650
1650
  writer.line("}");
1651
1651
  });
1652
1652
  writer.blank();
1653
+ writer.block(`suspend fun send(client: OkHttpClient, request: Request, retry: ${options.clientName}.RetryPolicy?): Response`, () => {
1654
+ writer.line("val idempotent = request.method in setOf(\"GET\", \"HEAD\", \"OPTIONS\", \"PUT\", \"DELETE\")");
1655
+ writer.line("val policy = retry?.takeIf { idempotent && request.body?.isOneShot() != true }");
1656
+ writer.line("var attempt = 1");
1657
+ writer.block("while (true)", () => {
1658
+ writer.line("val response = try {");
1659
+ writer.line(" execute(client, request)");
1660
+ writer.line("} catch (error: java.io.IOException) {");
1661
+ writer.line(" val delay = policy?.let { retryDelay(it, attempt, null) } ?: throw error");
1662
+ writer.line(" kotlinx.coroutines.delay(delay)");
1663
+ writer.line(" attempt += 1");
1664
+ writer.line(" continue");
1665
+ writer.line("}");
1666
+ writer.line("val delay = policy?.let { retryDelay(it, attempt, response) } ?: return response");
1667
+ writer.line("response.close()");
1668
+ writer.line("kotlinx.coroutines.delay(delay)");
1669
+ writer.line("attempt += 1");
1670
+ });
1671
+ });
1672
+ writer.blank();
1673
+ writer.block(`fun retryDelay(policy: ${options.clientName}.RetryPolicy, attempt: Int, response: Response?): Long?`, () => {
1674
+ writer.line("if (attempt >= policy.maximumAttempts) return null");
1675
+ writer.line("val ceiling = minOf(policy.maximumDelay.inWholeMilliseconds, policy.initialDelay.inWholeMilliseconds shl (attempt - 1))");
1676
+ writer.line("val backoff = (Math.random() * ceiling).toLong()");
1677
+ writer.line("if (response == null) return backoff");
1678
+ writer.line("if (response.code != 429 && response.code != 503) return null");
1679
+ writer.line("val asked = retryAfter(response.header(\"Retry-After\")) ?: return backoff");
1680
+ writer.line("return if (asked <= policy.maximumDelay.inWholeMilliseconds) asked else null");
1681
+ });
1682
+ writer.blank();
1683
+ writer.block("fun retryAfter(header: String?): Long?", () => {
1684
+ writer.line("val value = header?.trim() ?: return null");
1685
+ writer.line("value.toLongOrNull()?.let { return if (it >= 0) it * 1000 else null }");
1686
+ writer.line("val date = try {");
1687
+ writer.line(" java.time.ZonedDateTime.parse(value, java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME)");
1688
+ writer.line("} catch (_: java.time.format.DateTimeParseException) {");
1689
+ writer.line(" return null");
1690
+ writer.line("}");
1691
+ writer.line("return maxOf(0L, date.toInstant().toEpochMilli() - System.currentTimeMillis())");
1692
+ });
1693
+ writer.blank();
1653
1694
  writer.block("suspend fun execute(client: OkHttpClient, request: Request): Response", () => {
1654
1695
  writer.line("return kotlinx.coroutines.suspendCancellableCoroutine { continuation ->");
1655
1696
  writer.indent(() => {
@@ -1680,7 +1721,7 @@ const emitClient = (writer, config, partition, context, typesByOperation, regist
1680
1721
  const contextRequired = contextFields.some((field) => !hasDefault(field));
1681
1722
  const contextParam = contextFields.length > 0 ? `requestContext: RequestContext${contextRequired ? "" : " = RequestContext()"}, ` : "";
1682
1723
  writer.blank();
1683
- writer.block(`class ${clientName}(private val baseUrl: String, ${contextParam}private val client: OkHttpClient = OkHttpClient(), private val json: Json = Json { ignoreUnknownKeys = true }, private val requestInterceptor: (suspend (Request.Builder) -> Unit)? = null, private val responseInterceptor: (suspend (Request, Response) -> Unit)? = null)`, () => {
1724
+ writer.block(`class ${clientName}(private val baseUrl: String, ${contextParam}private val client: OkHttpClient = OkHttpClient(), private val json: Json = Json { ignoreUnknownKeys = true }, private val requestInterceptor: (suspend (Request.Builder) -> Unit)? = null, private val responseInterceptor: (suspend (Request, Response) -> Unit)? = null, private val retry: RetryPolicy? = null)`, () => {
1684
1725
  if (contextFields.length > 0) {
1685
1726
  writer.blank();
1686
1727
  writer.line("/** Values sent as headers on every request, from the api's request context. */");
@@ -1691,6 +1732,20 @@ const emitClient = (writer, config, partition, context, typesByOperation, regist
1691
1732
  else writer.line(`put(${stringLiteral(field.wireName)}, requestContext.${escapeKeyword(field.name)})`);
1692
1733
  });
1693
1734
  }
1735
+ writer.blank();
1736
+ writer.line("/**");
1737
+ writer.line(" * Retries a GET, HEAD, OPTIONS, PUT or DELETE that failed on the network or was answered 429 or 503,");
1738
+ writer.line(" * waiting what Retry-After asks or backing off.");
1739
+ writer.line(" *");
1740
+ writer.line(" * @property maximumAttempts Attempts in all, the first included.");
1741
+ writer.line(" * @property initialDelay What the first backoff waits at most.");
1742
+ writer.line(" * @property maximumDelay What any wait may take.");
1743
+ writer.line(" */");
1744
+ emitConstructorClass(writer, "data class RetryPolicy", [
1745
+ "val maximumAttempts: Int = 3",
1746
+ "val initialDelay: kotlin.time.Duration = 500.milliseconds",
1747
+ "val maximumDelay: kotlin.time.Duration = 30.seconds"
1748
+ ]);
1694
1749
  if (usesMultipart) {
1695
1750
  writer.blank();
1696
1751
  emitConstructorClass(writer, "data class MultipartFile", [
@@ -1746,7 +1801,7 @@ const emitClient = (writer, config, partition, context, typesByOperation, regist
1746
1801
  }
1747
1802
  if (groups.length > 0) for (const group of groups) {
1748
1803
  writer.blank();
1749
- writer.line(`val ${escapeKeyword(group.propertyName)} = ${group.className}(client, baseUrl, json, ${contextFields.length > 0 ? "requestContextHeaders" : "emptyMap()"}, requestInterceptor, responseInterceptor)`);
1804
+ writer.line(`val ${escapeKeyword(group.propertyName)} = ${group.className}(client, baseUrl, json, ${contextFields.length > 0 ? "requestContextHeaders" : "emptyMap()"}, requestInterceptor, responseInterceptor, retry)`);
1750
1805
  }
1751
1806
  for (const method of flatMethods) {
1752
1807
  writer.blank();
@@ -1791,6 +1846,8 @@ const renderKotlinClient = (api, partition, registry, config) => {
1791
1846
  writer.line("import okhttp3.*");
1792
1847
  writer.line("import okhttp3.MediaType.Companion.toMediaType");
1793
1848
  writer.line("import okhttp3.RequestBody.Companion.toRequestBody");
1849
+ writer.line("import kotlin.time.Duration.Companion.milliseconds");
1850
+ writer.line("import kotlin.time.Duration.Companion.seconds");
1794
1851
  writer.line("import okhttp3.HttpUrl.Companion.toHttpUrl");
1795
1852
  writer.blank();
1796
1853
  if (registry.all().some((type) => type.kind === "enum-class")) {
@@ -1847,7 +1904,10 @@ const renderKotlinClient = (api, partition, registry, config) => {
1847
1904
  });
1848
1905
  emitClient(writer, { clientName }, partition, context, typesByOperation, registry);
1849
1906
  for (const group of partition.groups) emitSubClientClass(writer, group, clientName, context);
1850
- emitKizunaObject(writer, { streaming: usesStreaming });
1907
+ emitKizunaObject(writer, {
1908
+ streaming: usesStreaming,
1909
+ clientName
1910
+ });
1851
1911
  for (const warning of registry.warnings()) process.stderr.write(`[kizuna/kotlin] JsonElement fallback at ${warning}\n`);
1852
1912
  return writer.toString();
1853
1913
  };
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_generator = require("./generator-D4SnsG2Q.cjs");
2
+ const require_generator = require("./generator-Bg4HlRb3.cjs");
3
3
  let kizunajs_generator = require("kizunajs/generator");
4
4
  let zod = require("zod");
5
5
  //#region src/client-target.ts
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as kotlinClientWalk, t as generateKotlinClient } from "./generator-DT7c2Vo3.mjs";
1
+ import { n as kotlinClientWalk, t as generateKotlinClient } from "./generator-D4GvH1-a.mjs";
2
2
  import { defineClient } from "kizunajs/generator";
3
3
  import { z } from "zod";
4
4
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kizunajs/kotlin",
3
- "version": "2.0.0-beta.19",
3
+ "version": "2.0.0-beta.20",
4
4
  "description": "Kotlin client generation for Kizuna.js APIs",
5
5
  "keywords": [
6
6
  "kizunajs",
@@ -60,14 +60,14 @@
60
60
  "access": "public"
61
61
  },
62
62
  "dependencies": {
63
- "@kizunajs/cli": "2.0.0-beta.19"
63
+ "@kizunajs/cli": "2.0.0-beta.20"
64
64
  },
65
65
  "peerDependencies": {
66
- "kizunajs": "2.0.0-beta.19",
66
+ "kizunajs": "2.0.0-beta.20",
67
67
  "zod": "^4.0.0"
68
68
  },
69
69
  "devDependencies": {
70
- "kizunajs": "2.0.0-beta.19",
70
+ "kizunajs": "2.0.0-beta.20",
71
71
  "@types/node": "^22.9.0",
72
72
  "tsdown": "^0.21.0",
73
73
  "typescript": "^6.0.3",