From 5f030565342e5ec1b3bbf5b1f3f7390c88fccfa9 Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Wed, 4 Feb 2026 21:06:05 -0500 Subject: [PATCH] feat: Adds createMany for rooms, in prep for parsing / uploading a csv file of room loads. --- .devcontainer/devcontainer.json | 21 ++++-- Sources/DatabaseClient/Interface.swift | 1 + Sources/DatabaseClient/Internal/Rooms.swift | 7 ++ .../Internal/Sequence+asyncMap.swift | 41 +++++++++++ TODO.md | 14 ++-- Tests/DatabaseClientTests/RoomTests.swift | 73 ++++--------------- 6 files changed, 87 insertions(+), 70 deletions(-) create mode 100644 Sources/DatabaseClient/Internal/Sequence+asyncMap.swift diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a496edd..4d4abd9 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,16 +6,14 @@ "version": "os-provided", "ppa": "false" }, - "ghcr.io/swift-server-community/swift-devcontainer-features/jemalloc:1": { }, - "ghcr.io/swift-server-community/swift-devcontainer-features/swift-format:0": { }, - "ghcr.io/swift-server-community/swift-devcontainer-features/foundationnetworking:1": {}, - "ghcr.io/devcontainers-extra/features/markdownlint-cli2:1": {}, "ghcr.io/jsburckhardt/devcontainer-features/just:1": {}, "ghcr.io/rocker-org/devcontainer-features/pandoc:1": {}, //"ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/wxw-matt/devcontainer-features/apt:latest": { "packages": "weasyprint gnupg2 tmux" - } + }, + "ghcr.io/swift-server-community/swift-devcontainer-features/jemalloc:1": { }, + }, "runArgs": [ "--cap-add=SYS_PTRACE", @@ -23,9 +21,20 @@ "seccomp=unconfined" ], "remoteEnv": { - "TERM": "xterm-256color" + // Set TERM, prevents problems when "xterm-ghostty" get's passed from host, + // which causes weird typing and display problems. + "TERM": "xterm-256color", + // Less backtrace error warnings from swift. + "SWIFT_BACKTRACE": "enable=no" }, "remoteUser": "swift", "forwardPorts": [8080], + "mounts": [ + { + "type": "bind", + "source": "${localEnv:HOME}/.local/share/nvim", + "target": "/home/swift/.local/share/nvim" + } + ] } diff --git a/Sources/DatabaseClient/Interface.swift b/Sources/DatabaseClient/Interface.swift index daf7862..35a657c 100644 --- a/Sources/DatabaseClient/Interface.swift +++ b/Sources/DatabaseClient/Interface.swift @@ -90,6 +90,7 @@ public struct DatabaseClient: Sendable { @DependencyClient public struct Rooms: Sendable { public var create: @Sendable (Room.Create) async throws -> Room + public var createMany: @Sendable ([Room.Create]) async throws -> [Room] public var delete: @Sendable (Room.ID) async throws -> Void public var deleteRectangularSize: @Sendable (Room.ID, Room.RectangularSize.ID) async throws -> Room diff --git a/Sources/DatabaseClient/Internal/Rooms.swift b/Sources/DatabaseClient/Internal/Rooms.swift index cfd8570..000517f 100644 --- a/Sources/DatabaseClient/Internal/Rooms.swift +++ b/Sources/DatabaseClient/Internal/Rooms.swift @@ -15,6 +15,13 @@ extension DatabaseClient.Rooms: TestDependencyKey { try await model.validateAndSave(on: database) return try model.toDTO() }, + createMany: { rooms in + try await rooms.asyncMap { request in + let model = try request.toModel() + try await model.validateAndSave(on: database) + return try model.toDTO() + } + }, delete: { id in guard let model = try await RoomModel.find(id, on: database) else { throw NotFoundError() diff --git a/Sources/DatabaseClient/Internal/Sequence+asyncMap.swift b/Sources/DatabaseClient/Internal/Sequence+asyncMap.swift new file mode 100644 index 0000000..b944d91 --- /dev/null +++ b/Sources/DatabaseClient/Internal/Sequence+asyncMap.swift @@ -0,0 +1,41 @@ +import Foundation + +extension Sequence { + + // Taken from: https://forums.swift.org/t/are-there-any-kind-of-asyncmap-method-for-a-normal-sequence/77354/7 + func asyncMap( + _ transform: @escaping @Sendable (Element) async throws -> Result + ) async rethrows -> [Result] where Element: Sendable { + try await withThrowingTaskGroup(of: (Int, Result).self) { group in + + var i = 0 + var iterator = self.makeIterator() + var results = [Result?]() + results.reserveCapacity(underestimatedCount) + + func submitTask() throws { + try Task.checkCancellation() + if let element = iterator.next() { + results.append(nil) + group.addTask { [i] in try await (i, transform(element)) } + i += 1 + } + + } + + // Add initial tasks + for _ in 0..