Sync with InsForge-sdk-js v1.5.0#30
Conversation
… SDK v1.5.0) Port InsForge/InsForge-sdk-js v1.5.0 storage changes (paired with the backend change InsForge/InsForge#1760): - upload(path:data:options:) / upload(path:fileURL:options:): uploading to an existing key now replaces the object in place (previously the server silently auto-renamed, e.g. photo.png -> photo (1).png). Signatures unchanged; docs updated. - upload(data:fileName:options:): now mints a unique, collision-free key client-side (sanitized base + timestamp + random suffix) and delegates to the standard upload(path:data:options:) path, so repeated uploads of the same file never overwrite each other. The backend no longer mints keys server-side. - Add offline unit tests for key generation in a new StorageObjectKeyTests class so they run in the deterministic CI suite (InsForgeStorageTests remains skipped as live-integration). - Update CHANGELOG (Unreleased) and README storage examples. Requires an InsForge backend that includes InsForge/InsForge#1760. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jwfing
left a comment
There was a problem hiding this comment.
Review: Sync with InsForge-sdk-js v1.5.0
Summary: A faithful, well-tested port of the JS SDK v1.5.0 storage change (standard PUT create-or-replace + client-side collision-free key minting for auto-upload); no blocking issues found.
Requirements context
No /docs/superpowers/ (or docs/specs/) directory exists in this repo — assessing against the PR description, the linked JS release (InsForge-sdk-js v1.5.0), and the backend contract (InsForge/InsForge#1760). I fetched the actual JS reference implementation (src/modules/storage.ts @ v1.5.0) to verify port parity directly rather than taking the parity claim on faith.
Findings
Critical
(none)
Suggestion
(none blocking — see Information.)
Information
Functionality / parity — algorithm matches JS byte-for-byte (confirmed). I diffed generateObjectKey (Sources/Storage/StorageClient.swift:375-403) against the JS generateObjectKey at v1.5.0. All branches line up:
hasExt(JSdotIndex > 0) ≡ Swiftif let dotIndex …, dotIndex != filename.startIndex— leading-dot/dotfile handling identical.- base sanitize
[^a-zA-Z0-9-_] → '-',slice(0,32)/.prefix(32), and|| 'file'empty-fallback all match (per-char map means map-then-truncate ≡ truncate-then-map). timestamp= ms epoch in both. Good parity; nothing to change.
Functionality — extension is copied verbatim, not sanitized or length-bounded (StorageClient.swift:378-379, 402). A crafted fileName (e.g. y.png/../a or a very long post-dot segment) injects /, whitespace, or unbounded length into the object key via ext. This is intentional parity — JS v1.5.0 does the exact same (ext = filename.slice(dotIndex)), so deviating here would break the sync and is correctly out of scope for this PR. Flagging only as a latent cross-SDK robustness note worth tracking upstream in the JS SDK first; the impact is low (object-storage key namespacing, not filesystem traversal), and the auto-key path's contract is collision-avoidance, not access control.
Software engineering — test coverage is solid and actually runs in CI. StorageObjectKeyTests (Tests/InsForgeStorageTests/StorageObjectKeyTests.swift) is a separate class from the live-integration InsForgeStorageTests, which is the class scripts/test-unit.sh skips — so the 7 new offline tests execute deterministically. @testable import InsForgeStorage correctly reaches the static internal generateObjectKey, and the target wiring in Package.swift:149-152 picks the file up. Coverage hits the meaningful edges (ext/base preservation, sanitization, 32-char truncation, no-ext, dotfile, empty→file, uniqueness). Regex assertions verified correct by hand (e.g. my photo (1) → my-photo--1- → matches ^my-photo--1--\d+…).
Functionality — auto-upload consolidation is clean. upload(data:fileName:options:) (StorageClient.swift:485-495) now delegates to upload(path:data:options:), which already handles both the confirmRequired and direct-upload/list fallback paths — so the removed bespoke logic is not a regression, and no methods were orphaned (getUploadStrategy/uploadToStrategy/confirmUpload/list remain used). Content-type inference is preserved: the generated key keeps the original extension, so inferContentType(from: path) (StorageClient.swift:1011-1012, keyed on pathExtension) yields the same result as the old inferContentType(from: fileName).
Software engineering — minor random-suffix divergence (cosmetic). Swift emits exactly 6 chars from [a-z0-9]; JS Math.random().toString(36).slice(2,8) can emit fewer than 6 (trailing-zero stripping). Both satisfy the [a-z0-9]+ contract and both are non-cryptographic; collision probability is negligible given the ms timestamp prefix. No action needed.
Security — no regressions. No new secret logging (debug logs only echo the path/key), no auth/authorization change, no new dependencies. The behavior change is server-driven (backend #1760); the SDK diff is client-side key derivation only.
Performance — no concerns. generateObjectKey is O(len(filename)); no new network round-trips (in fact the auto path is now a single delegated upload call rather than a separate strategy fetch).
Verdict: approved (informational)
Zero Critical findings. Faithful parity with JS v1.5.0 (verified against source), tests present and CI-visible, scope discipline is good (JS-only concerns explicitly skipped and justified). Posting as a comment per bot policy — the green-checkmark approval remains a separate human action.
One caveat to double-check before/at release, already called out in the PR body: this SDK now hard-requires a backend carrying InsForge/InsForge#1760 (standard-PUT semantics); running against a pre-change backend will silently auto-rename keys, defeating the create-or-replace contract.
Summary
Ports the storage changes from InsForge-sdk-js v1.5.0 (diff
v1.4.5..v1.5.0) — Storage: standard PUT create-or-replace semantics, paired with the backend change InsForge/InsForge#1760.What was ported
upload(path:data:options:)/upload(path:fileURL:options:)— uploading to a key that already exists now replaces the object in place (previously the server silently auto-renamed, e.g.photo.png→photo (1).png). This is a server-side behavior change; method signatures are unchanged. Doc comments updated to state the new semantics (mirrors the JS SDK, which only updated docs onupload).upload(data:fileName:options:)(the Swift equivalent of JSuploadAuto) — now mints a unique, collision-free key client-side (<sanitized-base>-<timestamp>-<random><ext>, base sanitized to[a-zA-Z0-9-_], truncated to 32 chars,filefallback — same algorithm as the JSgenerateObjectKey) and delegates to the standardupload(path:data:options:)path. Without this, the raw filename would be treated as the literal key by the new backend and repeated auto-key uploads would silently overwrite each other.StorageObjectKeyTestsclass (7 tests: ext/base preservation, sanitization, 32-char truncation, no-extension, dotfile, empty-name fallback, uniqueness), placed in a separate class so it runs in the deterministicscripts/test-unit.shsuite (the existingInsForgeStorageTestsclass is live-integration and skipped in CI).[Unreleased]entry; README storage examples annotated with the new semantics.What was skipped and why
storage.test.tsmock-fetch tests forgetPublicUrl/createSignedUrl/createSignedUrlsand the legacy-POST fallback — these cover JS-SDK methods that have no Swift counterpart and pre-existing JS behavior, not part of the v1.5.0 API contract change.package.json/ lockfile / CI workflow / SDK-REFERENCE plumbing — JS-repo concerns.scripts/release.shat release time, so the changelog entry sits under[Unreleased].Test results
swift build— passes (Swift 6.3.3, macOS arm64)../scripts/test-unit.sh(XCTest) — could not run locally: this machine has only CommandLineTools (no Xcode), so theXCTestmodule is unavailable. This is a pre-existing toolchain limitation, not caused by these changes; the repo's CI (macos-14+ full Xcode) will run the suite.swiftc -parse, and a standalone harness was compiled against the actual builtInsForgeStoragemodule (@testable import) exercisinggenerateObjectKeywith all 7 assertions from the new test class — all pass (e.g.report.pdf→report-1784608265685-hfp8nn.pdf, matching the JS/^report-\d+-[a-z0-9]+\.pdf$/contract)..swiftlint.ymlconventions.Notes
🤖 Generated with Claude Code
Summary by cubic
Syncs storage behavior with
InsForge-sdk-jsv1.5.0. Uploads now use standard PUT (create-or-replace), and auto-key uploads mint a unique client-side key to avoid overwrites. Requires backend with standard PUT storage (InsForge/InsForge#1760).Refactors
upload(path:data:...)andupload(path:fileURL:...)now replace existing objects in place (previously auto-renamed).upload(data:fileName:...)now generates a unique key client-side (<sanitized-base>-<timestamp>-<random><ext>) and delegates to path-based upload.Migration
upload(data:fileName:...)or provide unique paths.Written for commit f2854f6. Summary will update on new commits.