|
| 1 | +# Exporting Generic Swift APIs |
| 2 | + |
| 3 | +Expose generic functions, methods, initializers, and callbacks to JavaScript. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +Constrain each type parameter to `BridgedSwiftGenericBridgeable`. JavaScript callers select its concrete Swift type with a token from the generated `BridgeTypes` export. |
| 8 | + |
| 9 | +### Typed Swift-owned state |
| 10 | + |
| 11 | +One generic API can store and retrieve different bridged types without separate bindings for each type: |
| 12 | + |
| 13 | +```swift |
| 14 | +import JavaScriptKit |
| 15 | + |
| 16 | +@JS public final class ValueStore { |
| 17 | + private var values: [String: any BridgedSwiftGenericBridgeable] = [:] |
| 18 | + |
| 19 | + @JS public init<T: BridgedSwiftGenericBridgeable>(_ key: String, _ value: T) { |
| 20 | + values[key] = value |
| 21 | + } |
| 22 | + |
| 23 | + @JS public func put<T: BridgedSwiftGenericBridgeable>(_ key: String, _ value: T) { |
| 24 | + values[key] = value |
| 25 | + } |
| 26 | + |
| 27 | + @JS public func get<T: BridgedSwiftGenericBridgeable>(_ key: String) -> T? { |
| 28 | + values[key] as? T |
| 29 | + } |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +```javascript |
| 34 | +import { BridgeTypes } from "./bridge-js.js"; |
| 35 | + |
| 36 | +const store = new exports.ValueStore("count", 42, BridgeTypes.Int); |
| 37 | +store.put("title", "Draft", BridgeTypes.String); |
| 38 | +store.get("count", BridgeTypes.Int); // 42 |
| 39 | +store.get("title", BridgeTypes.String); // "Draft" |
| 40 | +store.get("count", BridgeTypes.String); // null: stored as Int |
| 41 | +store.release(); |
| 42 | +``` |
| 43 | + |
| 44 | +Pass one token per generic parameter after the regular arguments, in declaration order. Tokens use Swift names and underscore-separated namespaces, such as `BridgeTypes.API_Building`, even when the type has a separate JavaScript name. Import `BridgeTypes` from the generated module, not the `exports` object. |
| 45 | + |
| 46 | +Generic values may use `T`, `[T]`, `T?`, or `[String: T]`. Concrete parameters and results are also supported, such as `count<T>(_ values: [T]) -> Int`. See <doc:Supported-Types> for concrete types usable as `T`. |
| 47 | + |
| 48 | +### Async functions and callbacks |
| 49 | + |
| 50 | +Generic exports may be `async` and `throws(JSException)`. Callback parameters can use the same generic types and may be async, throwing, or `@escaping`: |
| 51 | + |
| 52 | +```swift |
| 53 | +@JS public func transform<T: BridgedSwiftGenericBridgeable, U: BridgedSwiftGenericBridgeable>( |
| 54 | + _ value: T, |
| 55 | + _ body: (T) async throws(JSException) -> U |
| 56 | +) async throws(JSException) -> U { |
| 57 | + try await body(value) |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +```javascript |
| 62 | +const text = await exports.transform( |
| 63 | + 42, async value => `Result: ${value}`, BridgeTypes.Int, BridgeTypes.String |
| 64 | +); |
| 65 | +``` |
| 66 | + |
| 67 | +Swift may retain an `@escaping` callback and invoke it after the registering call returns. The bridge keeps the JavaScript function alive until Swift releases the closure. Invoke it on the same JavaScript runtime/thread. Use `throws(JSException)` when a callback can throw or reject its promise. |
| 68 | + |
| 69 | +### Initializers and constraints |
| 70 | + |
| 71 | +Generic class initializers use `new`, as shown above; generic struct initializers use `exports.Type.init(...)`. A struct must also provide the nongeneric memberwise initializer used to reconstruct its stored fields. For a public struct, that initializer must be public or `@usableFromInline`. |
| 72 | + |
| 73 | +Additional `@JS` protocol constraints use compositions such as `T: BridgedSwiftGenericBridgeable & GraphNode`. Alternatively, let the protocol inherit the bridging requirement: |
| 74 | + |
| 75 | +```swift |
| 76 | +@JS public protocol GraphNode: BridgedSwiftGenericBridgeable { |
| 77 | + var id: String { get } |
| 78 | +} |
| 79 | + |
| 80 | +@JS public func identity<T: GraphNode>(_ node: T) -> T { node } |
| 81 | +``` |
| 82 | + |
| 83 | +The inherited requirement is recognized through protocol refinement and dependency modules. Concrete exported types still receive their bridging conformance automatically. Write `any GraphNode` when using the protocol as an existential value. |
| 84 | + |
| 85 | +The generated TypeScript signature preserves the constraint. Unknown or non-conforming tokens throw `TypeError` before arguments cross the bridge. See <doc:Exporting-Swift-Protocols> and <doc:Unsupported-Features> for remaining restrictions. |
0 commit comments