@kizunajs/swift 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-DBNFuaBB.cjs");
2
+ const require_generator = require("./generator-Bk6JRrTJ.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 generateSwiftClient } from "./generator-CjCPSdEz.mjs";
2
+ import { t as generateSwiftClient } from "./generator-2GeebFHv.mjs";
3
3
  import { mkdirSync, writeFileSync } from "node:fs";
4
4
  import { dirname, resolve } from "node:path";
5
5
  import { parseArgs } from "node:util";
@@ -1332,7 +1332,7 @@ const closure = (writer, opener, body) => {
1332
1332
  writer.indent(body);
1333
1333
  writer.line("}");
1334
1334
  };
1335
- const emitKizunaStreaming = (writer) => {
1335
+ const emitKizunaStreaming = (writer, clientName) => {
1336
1336
  writer.blank();
1337
1337
  writer.block("struct ServerSentEvent: Sendable", () => {
1338
1338
  writer.line("let event: String?");
@@ -1341,22 +1341,39 @@ const emitKizunaStreaming = (writer) => {
1341
1341
  writer.line("let retry: Int?");
1342
1342
  });
1343
1343
  writer.blank();
1344
- writer.block("static func open<Failure: KizunaFailure>(_ request: inout URLRequest, session: URLSession, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?, failure: Failure.Type) async throws(Failure) -> (URLSession.AsyncBytes, Int, HTTPURLResponse)", () => {
1344
+ writer.block(`static func open<Failure: KizunaFailure>(_ request: inout URLRequest, session: URLSession, retry: ${clientName}.RetryPolicy?, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?, failure: Failure.Type) async throws(Failure) -> (URLSession.AsyncBytes, Int, HTTPURLResponse)`, () => {
1345
1345
  writer.line("if let requestMiddleware {");
1346
1346
  writer.line(" do { try await requestMiddleware(&request) }");
1347
1347
  writer.line(" catch is CancellationError { throw Failure.cancelled }");
1348
1348
  writer.line(" catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
1349
1349
  writer.line(" catch { throw Failure.requestFailed(error) }");
1350
1350
  writer.line("}");
1351
- writer.line("let bytes: URLSession.AsyncBytes");
1352
- writer.line("let response: URLResponse");
1353
- writer.line("do { (bytes, response) = try await session.bytes(for: request) }");
1354
- writer.line("catch is CancellationError { throw Failure.cancelled }");
1355
- writer.line("catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
1356
- writer.line("catch { throw Failure.requestFailed(error) }");
1357
- writer.line("if let responseMiddleware { await responseMiddleware(request, Foundation.Data(), response) }");
1358
- writer.line("guard let httpResponse = response as? HTTPURLResponse else { throw Failure.invalidResponse }");
1359
- writer.line("return (bytes, httpResponse.statusCode, httpResponse)");
1351
+ writer.line("let policy = retryPolicy(for: request, retry)");
1352
+ writer.line("var attempt = 1");
1353
+ writer.block("while true", () => {
1354
+ writer.line("let bytes: URLSession.AsyncBytes");
1355
+ writer.line("let response: URLResponse");
1356
+ writer.line("do { (bytes, response) = try await session.bytes(for: request) }");
1357
+ writer.line("catch is CancellationError { throw Failure.cancelled }");
1358
+ writer.line("catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
1359
+ writer.line("catch {");
1360
+ writer.line(" if let policy, let delay = retryDelay(policy, attempt: attempt, response: nil) {");
1361
+ writer.line(" try await pause(delay, failure: Failure.self)");
1362
+ writer.line(" attempt += 1");
1363
+ writer.line(" continue");
1364
+ writer.line(" }");
1365
+ writer.line(" throw Failure.requestFailed(error)");
1366
+ writer.line("}");
1367
+ writer.line("if let responseMiddleware { await responseMiddleware(request, Foundation.Data(), response) }");
1368
+ writer.line("guard let httpResponse = response as? HTTPURLResponse else { throw Failure.invalidResponse }");
1369
+ writer.line("if let policy, let delay = retryDelay(policy, attempt: attempt, response: httpResponse) {");
1370
+ writer.line(" bytes.task.cancel()");
1371
+ writer.line(" try await pause(delay, failure: Failure.self)");
1372
+ writer.line(" attempt += 1");
1373
+ writer.line(" continue");
1374
+ writer.line("}");
1375
+ writer.line("return (bytes, httpResponse.statusCode, httpResponse)");
1376
+ });
1360
1377
  });
1361
1378
  writer.blank();
1362
1379
  writer.block("static func collect<Failure: KizunaFailure>(_ bytes: URLSession.AsyncBytes, failure: Failure.Type) async throws(Failure) -> Foundation.Data", () => {
@@ -1560,22 +1577,68 @@ const emitKizunaNamespace = (writer, options) => {
1560
1577
  writer.line("catch { throw Failure.requestFailed(error) }");
1561
1578
  });
1562
1579
  writer.blank();
1563
- writer.block("static func send<Failure: KizunaFailure>(_ request: inout URLRequest, session: URLSession, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?, failure: Failure.Type) async throws(Failure) -> (Foundation.Data, Int, HTTPURLResponse)", () => {
1580
+ writer.block(`static func send<Failure: KizunaFailure>(_ request: inout URLRequest, session: URLSession, retry: ${options.clientName}.RetryPolicy?, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?, failure: Failure.Type) async throws(Failure) -> (Foundation.Data, Int, HTTPURLResponse)`, () => {
1564
1581
  writer.line("if let requestMiddleware {");
1565
1582
  writer.line(" do { try await requestMiddleware(&request) }");
1566
1583
  writer.line(" catch is CancellationError { throw Failure.cancelled }");
1567
1584
  writer.line(" catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
1568
1585
  writer.line(" catch { throw Failure.requestFailed(error) }");
1569
1586
  writer.line("}");
1570
- writer.line("let data: Foundation.Data");
1571
- writer.line("let response: URLResponse");
1572
- writer.line("do { (data, response) = try await session.data(for: request) }");
1573
- writer.line("catch is CancellationError { throw Failure.cancelled }");
1574
- writer.line("catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
1575
- writer.line("catch { throw Failure.requestFailed(error) }");
1576
- writer.line("if let responseMiddleware { await responseMiddleware(request, data, response) }");
1577
- writer.line("guard let httpResponse = response as? HTTPURLResponse else { throw Failure.invalidResponse }");
1578
- writer.line("return (data, httpResponse.statusCode, httpResponse)");
1587
+ writer.line("let policy = retryPolicy(for: request, retry)");
1588
+ writer.line("var attempt = 1");
1589
+ writer.block("while true", () => {
1590
+ writer.line("let data: Foundation.Data");
1591
+ writer.line("let response: URLResponse");
1592
+ writer.line("do { (data, response) = try await session.data(for: request) }");
1593
+ writer.line("catch is CancellationError { throw Failure.cancelled }");
1594
+ writer.line("catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
1595
+ writer.line("catch {");
1596
+ writer.line(" if let policy, let delay = retryDelay(policy, attempt: attempt, response: nil) {");
1597
+ writer.line(" try await pause(delay, failure: Failure.self)");
1598
+ writer.line(" attempt += 1");
1599
+ writer.line(" continue");
1600
+ writer.line(" }");
1601
+ writer.line(" throw Failure.requestFailed(error)");
1602
+ writer.line("}");
1603
+ writer.line("if let responseMiddleware { await responseMiddleware(request, data, response) }");
1604
+ writer.line("guard let httpResponse = response as? HTTPURLResponse else { throw Failure.invalidResponse }");
1605
+ writer.line("if let policy, let delay = retryDelay(policy, attempt: attempt, response: httpResponse) {");
1606
+ writer.line(" try await pause(delay, failure: Failure.self)");
1607
+ writer.line(" attempt += 1");
1608
+ writer.line(" continue");
1609
+ writer.line("}");
1610
+ writer.line("return (data, httpResponse.statusCode, httpResponse)");
1611
+ });
1612
+ });
1613
+ writer.blank();
1614
+ writer.block(`static func retryPolicy(for request: URLRequest, _ retry: ${options.clientName}.RetryPolicy?) -> ${options.clientName}.RetryPolicy?`, () => {
1615
+ writer.line("let idempotent = [\"GET\", \"HEAD\", \"OPTIONS\", \"PUT\", \"DELETE\"].contains(request.httpMethod ?? \"GET\")");
1616
+ writer.line("return idempotent && request.httpBodyStream == nil ? retry : nil");
1617
+ });
1618
+ writer.blank();
1619
+ writer.block(`static func retryDelay(_ policy: ${options.clientName}.RetryPolicy, attempt: Int, response: HTTPURLResponse?) -> TimeInterval?`, () => {
1620
+ writer.line("guard attempt < policy.maximumAttempts else { return nil }");
1621
+ writer.line("let backoff = Double.random(in: 0...min(policy.maximumDelay, policy.initialDelay * pow(2, Double(attempt - 1))))");
1622
+ writer.line("guard let response else { return backoff }");
1623
+ writer.line("guard [429, 503].contains(response.statusCode) else { return nil }");
1624
+ writer.line("guard let asked = retryAfter(response.value(forHTTPHeaderField: \"Retry-After\")) else { return backoff }");
1625
+ writer.line("return asked <= policy.maximumDelay ? asked : nil");
1626
+ });
1627
+ writer.blank();
1628
+ writer.block("static func retryAfter(_ header: String?) -> TimeInterval?", () => {
1629
+ writer.line("guard let value = header?.trimmingCharacters(in: .whitespaces) else { return nil }");
1630
+ writer.line("if let seconds = Int(value), seconds >= 0 { return TimeInterval(seconds) }");
1631
+ writer.line("let formatter = DateFormatter()");
1632
+ writer.line("formatter.locale = Locale(identifier: \"en_US_POSIX\")");
1633
+ writer.line("formatter.timeZone = TimeZone(identifier: \"GMT\")");
1634
+ writer.line("formatter.dateFormat = \"EEE, dd MMM yyyy HH:mm:ss zzz\"");
1635
+ writer.line("guard let date = formatter.date(from: value) else { return nil }");
1636
+ writer.line("return max(0, date.timeIntervalSinceNow)");
1637
+ });
1638
+ writer.blank();
1639
+ writer.block("static func pause<Failure: KizunaFailure>(_ seconds: TimeInterval, failure: Failure.Type) async throws(Failure)", () => {
1640
+ writer.line("do { try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) }");
1641
+ writer.line("catch { throw Failure.cancelled }");
1579
1642
  });
1580
1643
  writer.blank();
1581
1644
  writer.block("static func decode<Value: Decodable, Failure: KizunaDecodableFailure>(_ type: Value.Type, from data: Foundation.Data, using decoder: JSONDecoder, statusCode: Int, failure: Failure.Type) throws(Failure) -> Value", () => {
@@ -1591,7 +1654,7 @@ const emitKizunaNamespace = (writer, options) => {
1591
1654
  writer.line("return Failure.decoding(DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: \"No matching type for status \\(statusCode)\")), statusCode: statusCode, data: data)");
1592
1655
  });
1593
1656
  }
1594
- if (options.streaming) emitKizunaStreaming(writer);
1657
+ if (options.streaming) emitKizunaStreaming(writer, options.clientName);
1595
1658
  if (options.multipart) {
1596
1659
  writer.blank();
1597
1660
  writer.block("struct MultipartBuilder", () => {
@@ -1734,7 +1797,7 @@ const emitMethodBody = (writer, method, context, receiver = "") => {
1734
1797
  emitStreamMethodTail(writer, method, context, receiver, responseBinding);
1735
1798
  return;
1736
1799
  }
1737
- writer.line(`let (data, statusCode, ${responseBinding}) = try await Kizuna.send(&request, session: ${receiver}session, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
1800
+ writer.line(`let (data, statusCode, ${responseBinding}) = try await Kizuna.send(&request, session: ${receiver}session, retry: ${receiver}retry, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
1738
1801
  writer.line("switch statusCode {");
1739
1802
  for (const successResponse of method.successResponses) {
1740
1803
  writer.line(`case ${successResponse.status}:`);
@@ -1809,7 +1872,7 @@ const emitStreamMethodTail = (writer, method, context, receiver, responseBinding
1809
1872
  const qualifiedResult = `${context.clientName}.${method.operationName}.${method.resultWrapperName}`;
1810
1873
  const hasHeaders = method.resultHeaderFields.length > 0;
1811
1874
  const collect = `let data = try await Kizuna.collect(bytes, failure: ${failure}.self)`;
1812
- writer.line(`let (bytes, statusCode, ${responseBinding}) = try await Kizuna.open(&request, session: ${receiver}session, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
1875
+ writer.line(`let (bytes, statusCode, ${responseBinding}) = try await Kizuna.open(&request, session: ${receiver}session, retry: ${receiver}retry, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
1813
1876
  writer.line("switch statusCode {");
1814
1877
  writer.line(`case ${stream.status}:`);
1815
1878
  writer.indent(() => {
@@ -1943,6 +2006,22 @@ const emitClient = (writer, config, partition, context, typesByOperation) => {
1943
2006
  });
1944
2007
  });
1945
2008
  }
2009
+ writer.blank();
2010
+ writer.docComment("Retries a GET, HEAD, OPTIONS, PUT or DELETE that failed on the network or was answered 429 or 503, waiting what Retry-After asks or backing off.");
2011
+ writer.block("public struct RetryPolicy: Sendable", () => {
2012
+ writer.docComment("Attempts in all, the first included.");
2013
+ writer.line("public var maximumAttempts: Int");
2014
+ writer.docComment("Seconds the first backoff waits at most.");
2015
+ writer.line("public var initialDelay: TimeInterval");
2016
+ writer.docComment("Seconds any wait may take.");
2017
+ writer.line("public var maximumDelay: TimeInterval");
2018
+ writer.blank();
2019
+ writer.block("public init(maximumAttempts: Int = 3, initialDelay: TimeInterval = 0.5, maximumDelay: TimeInterval = 30)", () => {
2020
+ writer.line("self.maximumAttempts = maximumAttempts");
2021
+ writer.line("self.initialDelay = initialDelay");
2022
+ writer.line("self.maximumDelay = maximumDelay");
2023
+ });
2024
+ });
1946
2025
  if (usesMultipart) {
1947
2026
  writer.blank();
1948
2027
  writer.block("public struct MultipartFile: Sendable, Equatable", () => {
@@ -2040,14 +2119,16 @@ const emitClient = (writer, config, partition, context, typesByOperation) => {
2040
2119
  if (contextFields.length > 0) writer.line("let requestContextHeaders: [String: String]");
2041
2120
  writer.line("public let requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?");
2042
2121
  writer.line("public let responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?");
2122
+ writer.line("public let retry: RetryPolicy?");
2043
2123
  writer.line("let encoder: JSONEncoder");
2044
2124
  writer.line("let decoder: JSONDecoder");
2045
2125
  writer.blank();
2046
2126
  const contextInitParam = contextFields.length > 0 ? `, requestContext: RequestContext${contextRequired ? "" : " = RequestContext()"}` : "";
2047
- writer.block(`public init(baseURL: URL, session: URLSession = .shared, timeout: TimeInterval = 30${contextInitParam}, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)? = nil, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)? = nil)`, () => {
2127
+ writer.block(`public init(baseURL: URL, session: URLSession = .shared, timeout: TimeInterval = 30, retry: RetryPolicy? = nil${contextInitParam}, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)? = nil, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)? = nil)`, () => {
2048
2128
  writer.line("self.baseURL = baseURL");
2049
2129
  writer.line("self.session = session");
2050
2130
  writer.line("self.timeout = timeout");
2131
+ writer.line("self.retry = retry");
2051
2132
  if (contextFields.length > 0) writer.line("self.requestContextHeaders = requestContext.headerFields");
2052
2133
  writer.line("self.requestMiddleware = requestMiddleware");
2053
2134
  writer.line("self.responseMiddleware = responseMiddleware");
@@ -1332,7 +1332,7 @@ const closure = (writer, opener, body) => {
1332
1332
  writer.indent(body);
1333
1333
  writer.line("}");
1334
1334
  };
1335
- const emitKizunaStreaming = (writer) => {
1335
+ const emitKizunaStreaming = (writer, clientName) => {
1336
1336
  writer.blank();
1337
1337
  writer.block("struct ServerSentEvent: Sendable", () => {
1338
1338
  writer.line("let event: String?");
@@ -1341,22 +1341,39 @@ const emitKizunaStreaming = (writer) => {
1341
1341
  writer.line("let retry: Int?");
1342
1342
  });
1343
1343
  writer.blank();
1344
- writer.block("static func open<Failure: KizunaFailure>(_ request: inout URLRequest, session: URLSession, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?, failure: Failure.Type) async throws(Failure) -> (URLSession.AsyncBytes, Int, HTTPURLResponse)", () => {
1344
+ writer.block(`static func open<Failure: KizunaFailure>(_ request: inout URLRequest, session: URLSession, retry: ${clientName}.RetryPolicy?, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?, failure: Failure.Type) async throws(Failure) -> (URLSession.AsyncBytes, Int, HTTPURLResponse)`, () => {
1345
1345
  writer.line("if let requestMiddleware {");
1346
1346
  writer.line(" do { try await requestMiddleware(&request) }");
1347
1347
  writer.line(" catch is CancellationError { throw Failure.cancelled }");
1348
1348
  writer.line(" catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
1349
1349
  writer.line(" catch { throw Failure.requestFailed(error) }");
1350
1350
  writer.line("}");
1351
- writer.line("let bytes: URLSession.AsyncBytes");
1352
- writer.line("let response: URLResponse");
1353
- writer.line("do { (bytes, response) = try await session.bytes(for: request) }");
1354
- writer.line("catch is CancellationError { throw Failure.cancelled }");
1355
- writer.line("catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
1356
- writer.line("catch { throw Failure.requestFailed(error) }");
1357
- writer.line("if let responseMiddleware { await responseMiddleware(request, Foundation.Data(), response) }");
1358
- writer.line("guard let httpResponse = response as? HTTPURLResponse else { throw Failure.invalidResponse }");
1359
- writer.line("return (bytes, httpResponse.statusCode, httpResponse)");
1351
+ writer.line("let policy = retryPolicy(for: request, retry)");
1352
+ writer.line("var attempt = 1");
1353
+ writer.block("while true", () => {
1354
+ writer.line("let bytes: URLSession.AsyncBytes");
1355
+ writer.line("let response: URLResponse");
1356
+ writer.line("do { (bytes, response) = try await session.bytes(for: request) }");
1357
+ writer.line("catch is CancellationError { throw Failure.cancelled }");
1358
+ writer.line("catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
1359
+ writer.line("catch {");
1360
+ writer.line(" if let policy, let delay = retryDelay(policy, attempt: attempt, response: nil) {");
1361
+ writer.line(" try await pause(delay, failure: Failure.self)");
1362
+ writer.line(" attempt += 1");
1363
+ writer.line(" continue");
1364
+ writer.line(" }");
1365
+ writer.line(" throw Failure.requestFailed(error)");
1366
+ writer.line("}");
1367
+ writer.line("if let responseMiddleware { await responseMiddleware(request, Foundation.Data(), response) }");
1368
+ writer.line("guard let httpResponse = response as? HTTPURLResponse else { throw Failure.invalidResponse }");
1369
+ writer.line("if let policy, let delay = retryDelay(policy, attempt: attempt, response: httpResponse) {");
1370
+ writer.line(" bytes.task.cancel()");
1371
+ writer.line(" try await pause(delay, failure: Failure.self)");
1372
+ writer.line(" attempt += 1");
1373
+ writer.line(" continue");
1374
+ writer.line("}");
1375
+ writer.line("return (bytes, httpResponse.statusCode, httpResponse)");
1376
+ });
1360
1377
  });
1361
1378
  writer.blank();
1362
1379
  writer.block("static func collect<Failure: KizunaFailure>(_ bytes: URLSession.AsyncBytes, failure: Failure.Type) async throws(Failure) -> Foundation.Data", () => {
@@ -1560,22 +1577,68 @@ const emitKizunaNamespace = (writer, options) => {
1560
1577
  writer.line("catch { throw Failure.requestFailed(error) }");
1561
1578
  });
1562
1579
  writer.blank();
1563
- writer.block("static func send<Failure: KizunaFailure>(_ request: inout URLRequest, session: URLSession, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?, failure: Failure.Type) async throws(Failure) -> (Foundation.Data, Int, HTTPURLResponse)", () => {
1580
+ writer.block(`static func send<Failure: KizunaFailure>(_ request: inout URLRequest, session: URLSession, retry: ${options.clientName}.RetryPolicy?, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?, failure: Failure.Type) async throws(Failure) -> (Foundation.Data, Int, HTTPURLResponse)`, () => {
1564
1581
  writer.line("if let requestMiddleware {");
1565
1582
  writer.line(" do { try await requestMiddleware(&request) }");
1566
1583
  writer.line(" catch is CancellationError { throw Failure.cancelled }");
1567
1584
  writer.line(" catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
1568
1585
  writer.line(" catch { throw Failure.requestFailed(error) }");
1569
1586
  writer.line("}");
1570
- writer.line("let data: Foundation.Data");
1571
- writer.line("let response: URLResponse");
1572
- writer.line("do { (data, response) = try await session.data(for: request) }");
1573
- writer.line("catch is CancellationError { throw Failure.cancelled }");
1574
- writer.line("catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
1575
- writer.line("catch { throw Failure.requestFailed(error) }");
1576
- writer.line("if let responseMiddleware { await responseMiddleware(request, data, response) }");
1577
- writer.line("guard let httpResponse = response as? HTTPURLResponse else { throw Failure.invalidResponse }");
1578
- writer.line("return (data, httpResponse.statusCode, httpResponse)");
1587
+ writer.line("let policy = retryPolicy(for: request, retry)");
1588
+ writer.line("var attempt = 1");
1589
+ writer.block("while true", () => {
1590
+ writer.line("let data: Foundation.Data");
1591
+ writer.line("let response: URLResponse");
1592
+ writer.line("do { (data, response) = try await session.data(for: request) }");
1593
+ writer.line("catch is CancellationError { throw Failure.cancelled }");
1594
+ writer.line("catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
1595
+ writer.line("catch {");
1596
+ writer.line(" if let policy, let delay = retryDelay(policy, attempt: attempt, response: nil) {");
1597
+ writer.line(" try await pause(delay, failure: Failure.self)");
1598
+ writer.line(" attempt += 1");
1599
+ writer.line(" continue");
1600
+ writer.line(" }");
1601
+ writer.line(" throw Failure.requestFailed(error)");
1602
+ writer.line("}");
1603
+ writer.line("if let responseMiddleware { await responseMiddleware(request, data, response) }");
1604
+ writer.line("guard let httpResponse = response as? HTTPURLResponse else { throw Failure.invalidResponse }");
1605
+ writer.line("if let policy, let delay = retryDelay(policy, attempt: attempt, response: httpResponse) {");
1606
+ writer.line(" try await pause(delay, failure: Failure.self)");
1607
+ writer.line(" attempt += 1");
1608
+ writer.line(" continue");
1609
+ writer.line("}");
1610
+ writer.line("return (data, httpResponse.statusCode, httpResponse)");
1611
+ });
1612
+ });
1613
+ writer.blank();
1614
+ writer.block(`static func retryPolicy(for request: URLRequest, _ retry: ${options.clientName}.RetryPolicy?) -> ${options.clientName}.RetryPolicy?`, () => {
1615
+ writer.line("let idempotent = [\"GET\", \"HEAD\", \"OPTIONS\", \"PUT\", \"DELETE\"].contains(request.httpMethod ?? \"GET\")");
1616
+ writer.line("return idempotent && request.httpBodyStream == nil ? retry : nil");
1617
+ });
1618
+ writer.blank();
1619
+ writer.block(`static func retryDelay(_ policy: ${options.clientName}.RetryPolicy, attempt: Int, response: HTTPURLResponse?) -> TimeInterval?`, () => {
1620
+ writer.line("guard attempt < policy.maximumAttempts else { return nil }");
1621
+ writer.line("let backoff = Double.random(in: 0...min(policy.maximumDelay, policy.initialDelay * pow(2, Double(attempt - 1))))");
1622
+ writer.line("guard let response else { return backoff }");
1623
+ writer.line("guard [429, 503].contains(response.statusCode) else { return nil }");
1624
+ writer.line("guard let asked = retryAfter(response.value(forHTTPHeaderField: \"Retry-After\")) else { return backoff }");
1625
+ writer.line("return asked <= policy.maximumDelay ? asked : nil");
1626
+ });
1627
+ writer.blank();
1628
+ writer.block("static func retryAfter(_ header: String?) -> TimeInterval?", () => {
1629
+ writer.line("guard let value = header?.trimmingCharacters(in: .whitespaces) else { return nil }");
1630
+ writer.line("if let seconds = Int(value), seconds >= 0 { return TimeInterval(seconds) }");
1631
+ writer.line("let formatter = DateFormatter()");
1632
+ writer.line("formatter.locale = Locale(identifier: \"en_US_POSIX\")");
1633
+ writer.line("formatter.timeZone = TimeZone(identifier: \"GMT\")");
1634
+ writer.line("formatter.dateFormat = \"EEE, dd MMM yyyy HH:mm:ss zzz\"");
1635
+ writer.line("guard let date = formatter.date(from: value) else { return nil }");
1636
+ writer.line("return max(0, date.timeIntervalSinceNow)");
1637
+ });
1638
+ writer.blank();
1639
+ writer.block("static func pause<Failure: KizunaFailure>(_ seconds: TimeInterval, failure: Failure.Type) async throws(Failure)", () => {
1640
+ writer.line("do { try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) }");
1641
+ writer.line("catch { throw Failure.cancelled }");
1579
1642
  });
1580
1643
  writer.blank();
1581
1644
  writer.block("static func decode<Value: Decodable, Failure: KizunaDecodableFailure>(_ type: Value.Type, from data: Foundation.Data, using decoder: JSONDecoder, statusCode: Int, failure: Failure.Type) throws(Failure) -> Value", () => {
@@ -1591,7 +1654,7 @@ const emitKizunaNamespace = (writer, options) => {
1591
1654
  writer.line("return Failure.decoding(DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: \"No matching type for status \\(statusCode)\")), statusCode: statusCode, data: data)");
1592
1655
  });
1593
1656
  }
1594
- if (options.streaming) emitKizunaStreaming(writer);
1657
+ if (options.streaming) emitKizunaStreaming(writer, options.clientName);
1595
1658
  if (options.multipart) {
1596
1659
  writer.blank();
1597
1660
  writer.block("struct MultipartBuilder", () => {
@@ -1734,7 +1797,7 @@ const emitMethodBody = (writer, method, context, receiver = "") => {
1734
1797
  emitStreamMethodTail(writer, method, context, receiver, responseBinding);
1735
1798
  return;
1736
1799
  }
1737
- writer.line(`let (data, statusCode, ${responseBinding}) = try await Kizuna.send(&request, session: ${receiver}session, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
1800
+ writer.line(`let (data, statusCode, ${responseBinding}) = try await Kizuna.send(&request, session: ${receiver}session, retry: ${receiver}retry, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
1738
1801
  writer.line("switch statusCode {");
1739
1802
  for (const successResponse of method.successResponses) {
1740
1803
  writer.line(`case ${successResponse.status}:`);
@@ -1809,7 +1872,7 @@ const emitStreamMethodTail = (writer, method, context, receiver, responseBinding
1809
1872
  const qualifiedResult = `${context.clientName}.${method.operationName}.${method.resultWrapperName}`;
1810
1873
  const hasHeaders = method.resultHeaderFields.length > 0;
1811
1874
  const collect = `let data = try await Kizuna.collect(bytes, failure: ${failure}.self)`;
1812
- writer.line(`let (bytes, statusCode, ${responseBinding}) = try await Kizuna.open(&request, session: ${receiver}session, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
1875
+ writer.line(`let (bytes, statusCode, ${responseBinding}) = try await Kizuna.open(&request, session: ${receiver}session, retry: ${receiver}retry, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
1813
1876
  writer.line("switch statusCode {");
1814
1877
  writer.line(`case ${stream.status}:`);
1815
1878
  writer.indent(() => {
@@ -1943,6 +2006,22 @@ const emitClient = (writer, config, partition, context, typesByOperation) => {
1943
2006
  });
1944
2007
  });
1945
2008
  }
2009
+ writer.blank();
2010
+ writer.docComment("Retries a GET, HEAD, OPTIONS, PUT or DELETE that failed on the network or was answered 429 or 503, waiting what Retry-After asks or backing off.");
2011
+ writer.block("public struct RetryPolicy: Sendable", () => {
2012
+ writer.docComment("Attempts in all, the first included.");
2013
+ writer.line("public var maximumAttempts: Int");
2014
+ writer.docComment("Seconds the first backoff waits at most.");
2015
+ writer.line("public var initialDelay: TimeInterval");
2016
+ writer.docComment("Seconds any wait may take.");
2017
+ writer.line("public var maximumDelay: TimeInterval");
2018
+ writer.blank();
2019
+ writer.block("public init(maximumAttempts: Int = 3, initialDelay: TimeInterval = 0.5, maximumDelay: TimeInterval = 30)", () => {
2020
+ writer.line("self.maximumAttempts = maximumAttempts");
2021
+ writer.line("self.initialDelay = initialDelay");
2022
+ writer.line("self.maximumDelay = maximumDelay");
2023
+ });
2024
+ });
1946
2025
  if (usesMultipart) {
1947
2026
  writer.blank();
1948
2027
  writer.block("public struct MultipartFile: Sendable, Equatable", () => {
@@ -2040,14 +2119,16 @@ const emitClient = (writer, config, partition, context, typesByOperation) => {
2040
2119
  if (contextFields.length > 0) writer.line("let requestContextHeaders: [String: String]");
2041
2120
  writer.line("public let requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?");
2042
2121
  writer.line("public let responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?");
2122
+ writer.line("public let retry: RetryPolicy?");
2043
2123
  writer.line("let encoder: JSONEncoder");
2044
2124
  writer.line("let decoder: JSONDecoder");
2045
2125
  writer.blank();
2046
2126
  const contextInitParam = contextFields.length > 0 ? `, requestContext: RequestContext${contextRequired ? "" : " = RequestContext()"}` : "";
2047
- writer.block(`public init(baseURL: URL, session: URLSession = .shared, timeout: TimeInterval = 30${contextInitParam}, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)? = nil, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)? = nil)`, () => {
2127
+ writer.block(`public init(baseURL: URL, session: URLSession = .shared, timeout: TimeInterval = 30, retry: RetryPolicy? = nil${contextInitParam}, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)? = nil, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)? = nil)`, () => {
2048
2128
  writer.line("self.baseURL = baseURL");
2049
2129
  writer.line("self.session = session");
2050
2130
  writer.line("self.timeout = timeout");
2131
+ writer.line("self.retry = retry");
2051
2132
  if (contextFields.length > 0) writer.line("self.requestContextHeaders = requestContext.headerFields");
2052
2133
  writer.line("self.requestMiddleware = requestMiddleware");
2053
2134
  writer.line("self.responseMiddleware = responseMiddleware");
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_generator = require("./generator-DBNFuaBB.cjs");
2
+ const require_generator = require("./generator-Bk6JRrTJ.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 swiftClientWalk, t as generateSwiftClient } from "./generator-CjCPSdEz.mjs";
1
+ import { n as swiftClientWalk, t as generateSwiftClient } from "./generator-2GeebFHv.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/swift",
3
- "version": "2.0.0-beta.19",
3
+ "version": "2.0.0-beta.20",
4
4
  "description": "Swift client generation for Kizuna.js APIs",
5
5
  "keywords": [
6
6
  "kizunajs",
@@ -59,14 +59,14 @@
59
59
  "access": "public"
60
60
  },
61
61
  "dependencies": {
62
- "@kizunajs/cli": "2.0.0-beta.19"
62
+ "@kizunajs/cli": "2.0.0-beta.20"
63
63
  },
64
64
  "peerDependencies": {
65
- "kizunajs": "2.0.0-beta.19",
65
+ "kizunajs": "2.0.0-beta.20",
66
66
  "zod": "^4.0.0"
67
67
  },
68
68
  "devDependencies": {
69
- "kizunajs": "2.0.0-beta.19",
69
+ "kizunajs": "2.0.0-beta.20",
70
70
  "@types/node": "^24",
71
71
  "tsdown": "^0.21.0",
72
72
  "typescript": "^6.0.3",