Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// swift-tools-version: 5.9
//
// Moss iOS SDK — Swift Package Manager manifest.
//
// The `Moss` library wraps the precompiled `MossC` xcframework hosted as a
// GitHub Release asset on this repo. Xcode downloads the binary on first
// resolve, verifies the SHA-256 checksum below, and links it into the
// consuming target.
//
// To consume from another package or app:
//
// dependencies: [
// .package(url: "https://github.com/usemoss/moss", from: "0.1.0"),
// ],
// targets: [
// .target(name: "YourTarget", dependencies: [
// .product(name: "Moss", package: "moss"),
// ]),
// ]
//
// Or, in Xcode: File ▸ Add Package Dependencies ▸ https://github.com/usemoss/moss
//
// The Swift wrapper sources live under `sdks/swift/Sources/Moss/`. The
// xcframework binary is not committed to this repo — it ships as a release
// asset. Bump both the URL tag segment and the checksum together on every
// new tag.
import PackageDescription

let package = Package(
name: "Moss",
platforms: [.iOS(.v15)],
products: [
.library(name: "Moss", targets: ["Moss"]),
],
targets: [
.binaryTarget(
name: "MossC",
url: "https://github.com/usemoss/moss/releases/download/v0.1.0/Moss.xcframework.zip",
checksum: "08d78183b3eb94a372990373fd669101bb3292d5824680b2f5b826977a9ee924"
),
.target(
name: "Moss",
dependencies: ["MossC"],
path: "sdks/swift/Sources/Moss"
),
]
)
24 changes: 24 additions & 0 deletions sdks/swift/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
BSD 2-Clause License

Copyright (c) 2025 InferEdge Inc.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
121 changes: 121 additions & 0 deletions sdks/swift/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Moss Swift SDK

The Swift SDK for [Moss](https://github.com/usemoss/moss) — fast on-device search for iOS.

## Requirements

- iOS 15+
- Xcode 15+

## Install

In Xcode: **File ▸ Add Package Dependencies…** and enter

```
https://github.com/usemoss/moss
```

Pick the latest version under "Up to Next Major Version".

Or in `Package.swift`:

```swift
dependencies: [
.package(url: "https://github.com/usemoss/moss", from: "0.1.0"),
],
targets: [
.target(name: "YourTarget", dependencies: [
.product(name: "Moss", package: "moss"),
]),
]
```

## Quick start

```swift
import Moss

let client = try MossClient(projectId: "your_project_id", projectKey: "your_project_key")
defer { client.close() }

// Create an index, load it, query.
_ = try await client.createIndex("support-docs", docs: [
.init(id: "1", text: "Refunds are processed within 3-5 business days."),
.init(id: "2", text: "You can track your order on the dashboard."),
])

try await client.loadIndex("support-docs")

let result = try await client.query("support-docs", "how long do refunds take?")
for doc in result.docs {
print(String(format: "[%.3f] %@", doc.score, doc.text))
}
```

## Authentication

Two ways to authenticate:

**Static project key** — simplest, fine for prototyping:

```swift
let client = try MossClient(projectId: id, projectKey: key)
```

**`Authenticator` protocol** — for apps that fetch short-lived tokens from
your backend:

```swift
final class MyAuth: Authenticator {
func getAuthHeader() async throws -> String {
// Fetch / refresh a bearer token from your server.
return try await myServer.fetchToken()
}
}

let client = try MossClient(projectId: id, authenticator: MyAuth())
```

## Memory pressure

When the OS sends a memory warning, ask the SDK to drop reclaimable
caches:

```swift
NotificationCenter.default.addObserver(
forName: UIApplication.didReceiveMemoryWarningNotification,
object: nil,
queue: .main
) { _ in
Task { _ = try? await client.onMemoryPressure(.critical) }
}
```

On-disk caches are kept; only in-memory structures are freed. Next
`loadIndex` rehydrates from disk.

## Threading

All operations are `async throws` and dispatch work onto a background
thread. The underlying client is thread-safe — you can share a single
`MossClient` across the app.

## Custom cache directory (advanced)

`MossClient` automatically caches model files under
`<Library/Caches>/moss-models/`. To point it somewhere else — e.g. a
shared App Group container so multiple targets share the same models —
call `setModelCacheDir` *before* constructing your first client:

```swift
try MossClient.setModelCacheDir("/path/to/your/cache")
let client = try MossClient(projectId: id, projectKey: key)
```

## Reporting issues

Open an issue at <https://github.com/usemoss/moss/issues>.

## License

[BSD 2-Clause](./LICENSE)
51 changes: 51 additions & 0 deletions sdks/swift/Sources/Moss/Authenticator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Foundation
import MossC

/// Implement to inject a custom auth flow into [MossClient].
///
/// The native runtime calls [getAuthHeader] whenever it needs a fresh bearer
/// token for an outbound request. Implementations typically fetch the token
/// from your backend (and cache it until expiry).
///
/// ## Return-value contract
///
/// Return **the raw bearer token only** — do **not** include the `Bearer `
/// prefix or any other Authorization-header decoration:
///
/// ```swift
/// // ✅ correct
/// return "eyJhbGciOi..."
/// // ❌ wrong — the SDK prepends `Bearer ` itself
/// return "Bearer eyJhbGciOi..."
/// ```
///
/// The Swift wrapper passes this string directly to the native side, which
/// constructs the full `Authorization: Bearer <token>` header. The JS SDK's
/// `IAuthenticator.getAuthHeader()` happens to use the opposite convention
/// (returns the full `Bearer ...` value); that's because the JS SDK builds
/// the request in JS userland rather than going through the native C ABI.
/// Don't copy the JS convention here.
///
/// Implementations must be safe to call from any thread; the native side may
/// invoke from a background worker.
public protocol Authenticator: AnyObject, Sendable {
func getAuthHeader() async throws -> String
}
Comment on lines +6 to +33

// ── Internal C-callback dispatch ─────────────────────────────────────

/// Holds a strong reference to the user's authenticator so the C callback can
/// dispatch back. The pointer to this box becomes the `user_data` passed to
/// `moss_client_new_with_authenticator`. The actual C trampoline lives in
/// MossClient.swift to avoid Swift emitting duplicate `@_cdecl` symbols
/// across translation units that reference it (eager linking would
/// otherwise reject the build).
///
/// `@unchecked Sendable`: the box only holds an immutable `any Authenticator`,
/// and the `Authenticator` protocol itself requires `Sendable`. The Swift
/// compiler can't see that across the `Unmanaged.fromOpaque` boundary —
/// hence `@unchecked`.
final class AuthenticatorBox: @unchecked Sendable {
let inner: any Authenticator
init(_ inner: any Authenticator) { self.inner = inner }
}
Loading
Loading