Skip to content

Commit 57bf70f

Browse files
ampagenttmm
andcommitted
refactor(swift): keep the engine out of the public API
The IVM operators, wire-protocol bodies, B-tree, hashes, and JSON machinery were public, so apps (and the docs) saw ~1,700 symbols. They are package now: the package's tests, ZeroTesting, and the benchmarks still see them; apps see what they and the generated code use (~720). ErroredQuery and escapeLike stay public (views report the one; zbugs uses the other). zbugs reads JWT claims with JSONDecoder instead of the engine's parser. A package type with @inlinable members has to be @usableFromInline, or other modules quietly get unspecialized calls: BTreeSet<Int> scans from the benchmarks were 5x slower until it was. Amp-Thread-ID: https://ampcode.com/threads/T-c513a037-a257-45e0-b9a9-15c15edfe859 Co-authored-by: tmm <tmm@tmm.dev>
1 parent efd4711 commit 57bf70f

50 files changed

Lines changed: 602 additions & 593 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/zbugs-swift/Sources/ZBugs/Auth.swift‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Foundation
12
import Zero
23

34
/// Who the client acts for: the `sub` and `role` of the zbugs JWT
@@ -97,7 +98,7 @@ extension AuthData {
9798
public init?(jwt: String, now: Double) {
9899
let parts = jwt.split(separator: ".", omittingEmptySubsequences: false)
99100
guard parts.count == 3, let payload = base64URLDecode(parts[1]),
100-
let claims = try? JSONParser.parse(payload),
101+
let claims = try? JSONDecoder().decode(JSONValue.self, from: Data(payload)),
101102
let sub = claims["sub"]?.stringValue,
102103
let role = claims["role"]?.stringValue.flatMap(UserRole.init(rawValue:))
103104
else { return nil }

‎swift/DESIGN.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ apps/zbugs-swift/ dogfood app (iOS + macOS)
7474
in this public monorepo either way.
7575
- SwiftPM products: `Zero`, `ZeroUI`, `ZeroTesting`, and `ZeroNIOTesting`
7676
(test-only; the only product that depends on SwiftNIO).
77+
- The public API is what apps and the generated code use. The engine (IVM
78+
operators, the wire protocol, the B-tree, hashes) is `package`: the
79+
package's own modules (tests, `ZeroTesting`, benchmarks) see it, apps don't.
80+
A `package` type with `@inlinable` members must itself be
81+
`@usableFromInline`, or other modules silently get unspecialized calls.
7782
- The CLI ships as the unscoped npm package `zero-swift` (we don't own the
7883
`@rocicorp` scope) with a peer dependency on `@rocicorp/zero`. It is
7984
versioned with the Swift releases.

‎swift/Sources/Zero/AST/ASTCoding.swift‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// JSON encoding of the AST, following `astSchema`.
22
extension AST: JSONCodable {
3-
public init(json: JSONValue) throws(DecodingError) {
3+
package init(json: JSONValue) throws(DecodingError) {
44
let r = try JSONObjectReader(json, "AST")
55
schema = try r.optionalString("schema")
66
table = try r.string("table")
@@ -25,7 +25,7 @@ extension AST: JSONCodable {
2525
}
2626
}
2727

28-
public var json: JSONValue {
28+
package var json: JSONValue {
2929
var o = JSONObjectBuilder()
3030
o.set("schema", schema)
3131
o.set("table", .string(table))
@@ -46,7 +46,7 @@ extension AST: JSONCodable {
4646
}
4747

4848
extension AST.CorrelatedSubquery: JSONCodable {
49-
public init(json: JSONValue) throws(DecodingError) {
49+
package init(json: JSONValue) throws(DecodingError) {
5050
let r = try JSONObjectReader(json, "correlated subquery")
5151
correlation = try r.required("correlation") { (v) throws(DecodingError) in
5252
let c = try JSONObjectReader(v, "correlation")
@@ -65,7 +65,7 @@ extension AST.CorrelatedSubquery: JSONCodable {
6565
hidden = try r.optionalBool("hidden")
6666
}
6767

68-
public var json: JSONValue {
68+
package var json: JSONValue {
6969
var o = JSONObjectBuilder()
7070
o.set(
7171
"correlation",
@@ -80,7 +80,7 @@ extension AST.CorrelatedSubquery: JSONCodable {
8080
}
8181

8282
extension AST.ValuePosition: JSONCodable {
83-
public init(json: JSONValue) throws(DecodingError) {
83+
package init(json: JSONValue) throws(DecodingError) {
8484
let r = try JSONObjectReader(json, "value position")
8585
let type = try r.string("type")
8686
switch type {
@@ -120,7 +120,7 @@ extension AST.ValuePosition: JSONCodable {
120120
}
121121
}
122122

123-
public var json: JSONValue {
123+
package var json: JSONValue {
124124
switch self {
125125
case .literal(let v): ["type": "literal", "value": v]
126126
case .column(let name): ["type": "column", "name": .string(name)]
@@ -131,7 +131,7 @@ extension AST.ValuePosition: JSONCodable {
131131
}
132132

133133
extension AST.Condition: JSONCodable {
134-
public init(json: JSONValue) throws(DecodingError) {
134+
package init(json: JSONValue) throws(DecodingError) {
135135
let r = try JSONObjectReader(json, "condition")
136136
let type = try r.string("type")
137137
switch type {
@@ -166,7 +166,7 @@ extension AST.Condition: JSONCodable {
166166
}
167167
}
168168

169-
public var json: JSONValue {
169+
package var json: JSONValue {
170170
switch self {
171171
case .simple(let op, let left, let right):
172172
return ["type": "simple", "op": .string(op.rawValue), "left": left.json, "right": right.json]

‎swift/Sources/Zero/AST/ASTNormalize.swift‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/// `related` sorted by alias, `where` flattened and sorted. Two ASTs that
33
/// describe the same query hash the same once normalized.
44
extension AST {
5-
public func normalized() -> AST {
5+
package func normalized() -> AST {
66
var ast = self
77
ast.where = `where`.map { Self.normalizeSubqueries($0) }.map(Self.normalizeCondition)
88
if let related {
@@ -37,7 +37,7 @@ extension AST {
3737
return related
3838
}
3939

40-
public static func normalizeCondition(_ cond: Condition) -> Condition {
40+
package static func normalizeCondition(_ cond: Condition) -> Condition {
4141
switch cond {
4242
case .simple, .correlatedSubquery:
4343
return cond

‎swift/Sources/Zero/Builder/Builder.swift‎

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// Connects a pipeline being built to its environment
22
/// (`BuilderDelegate` in `packages/zql/src/builder/builder.ts`).
3-
public protocol BuilderDelegate: AnyObject {
3+
package protocol BuilderDelegate: AnyObject {
44
/// Apply `where` filters even when the source applies them all.
55
var applyFiltersAnyway: Bool { get }
66
/// Allow NOT EXISTS. Only the server does: a client cannot tell a row
@@ -18,16 +18,16 @@ public protocol BuilderDelegate: AnyObject {
1818
}
1919

2020
extension BuilderDelegate {
21-
public var applyFiltersAnyway: Bool { false }
22-
public var enableNotExists: Bool { false }
23-
public var disableCorrelatedPredicatePushdown: Bool { false }
24-
public func decorateInput(_ input: any Input, name: String) -> any Input { input }
25-
public func addEdge(_ source: any InputBase, _ dest: any InputBase) {}
26-
public func decorateFilterInput(_ input: any FilterInput, name: String) -> any FilterInput {
21+
package var applyFiltersAnyway: Bool { false }
22+
package var enableNotExists: Bool { false }
23+
package var disableCorrelatedPredicatePushdown: Bool { false }
24+
package func decorateInput(_ input: any Input, name: String) -> any Input { input }
25+
package func addEdge(_ source: any InputBase, _ dest: any InputBase) {}
26+
package func decorateFilterInput(_ input: any FilterInput, name: String) -> any FilterInput {
2727
input
2828
}
29-
public func decorateSourceInput(_ input: any SourceInput, queryID: String) -> any Input { input }
30-
public func mapAst(_ ast: AST) -> AST { ast }
29+
package func decorateSourceInput(_ input: any SourceInput, queryID: String) -> any Input { input }
30+
package func mapAst(_ ast: AST) -> AST { ast }
3131
}
3232

3333
/// The limit put on EXISTS subqueries: one row is enough, a few more keep
@@ -36,7 +36,7 @@ let existsLimit = 3
3636
let permissionsExistsLimit = 1
3737

3838
/// Builds a pipeline from an AST (`buildPipeline`).
39-
public func buildPipeline(
39+
package func buildPipeline(
4040
_ ast: AST, _ delegate: any BuilderDelegate, queryID: String
4141
)
4242
throws(PipelineError) -> any Input
@@ -55,18 +55,18 @@ public func buildPipeline(
5555
}
5656

5757
/// Values for static parameters (`StaticQueryParameters`).
58-
public struct StaticQueryParameters {
59-
public var authData: [String: JSONValue]?
60-
public var preMutationRow: Row?
58+
package struct StaticQueryParameters {
59+
package var authData: [String: JSONValue]?
60+
package var preMutationRow: Row?
6161

62-
public init(authData: [String: JSONValue]?, preMutationRow: Row? = nil) {
62+
package init(authData: [String: JSONValue]?, preMutationRow: Row? = nil) {
6363
self.authData = authData
6464
self.preMutationRow = preMutationRow
6565
}
6666
}
6767

6868
/// `bindStaticParameters`: replaces static parameters with their values.
69-
public func bindStaticParameters(_ ast: AST, _ params: StaticQueryParameters?) -> AST {
69+
package func bindStaticParameters(_ ast: AST, _ params: StaticQueryParameters?) -> AST {
7070
func bindValue(_ v: AST.ValuePosition) -> AST.ValuePosition {
7171
guard case .static(let anchor, let field) = v else { return v }
7272
guard let params else { preconditionFailure("Static query params do not exist") }

‎swift/Sources/Zero/Client/ConnectionError.swift‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// The kinds of error the client raises itself
22
/// (`packages/zero-client/src/client/client-error-kind-enum.ts`).
3-
public enum ClientErrorKind: String, Sendable, Hashable, CaseIterable {
3+
package enum ClientErrorKind: String, Sendable, Hashable, CaseIterable {
44
case abruptClose = "AbruptClose"
55
case cleanClose = "CleanClose"
66
case clientClosed = "ClientClosed"
@@ -19,26 +19,26 @@ public enum ClientErrorKind: String, Sendable, Hashable, CaseIterable {
1919
/// Why a connection ended or could not be made: an error the client raised
2020
/// (`ClientError`) or one the server sent (`ProtocolError`), together
2121
/// `ZeroError` in `packages/zero-client/src/client/error.ts`.
22-
public enum ConnectionError: Error, Sendable, Hashable, CustomStringConvertible {
22+
package enum ConnectionError: Error, Sendable, Hashable, CustomStringConvertible {
2323
case client(ClientErrorKind, message: String)
2424
case server(ErrorBody)
2525

26-
public var message: String {
26+
package var message: String {
2727
switch self {
2828
case .client(_, let message): message
2929
case .server(let body): body.message
3030
}
3131
}
3232

3333
/// The kind's name, a `ClientErrorKind` or an `ErrorKind`.
34-
public var kindName: String {
34+
package var kindName: String {
3535
switch self {
3636
case .client(let kind, _): kind.rawValue
3737
case .server(let body): body.kind.rawValue
3838
}
3939
}
4040

41-
public var description: String { "\(kindName): \(message)" }
41+
package var description: String { "\(kindName): \(message)" }
4242

4343
var clientKind: ClientErrorKind? {
4444
if case .client(let kind, _) = self { return kind }

‎swift/Sources/Zero/Client/ConnectionManager.swift‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ public enum ConnectionState: Sendable, Hashable {
313313
}
314314

315315
/// The JSON form of the JS `ConnectionState`.
316-
public var json: JSONValue {
316+
package var json: JSONValue {
317317
var o: [String: JSONValue] = ["name": .string(status.rawValue)]
318318
switch self {
319319
case .initializing, .connected: break
@@ -353,7 +353,7 @@ public enum NeedsAuthReason: Sendable, Hashable {
353353
self = .zeroCache(reason: error.message)
354354
}
355355

356-
public var json: JSONValue {
356+
package var json: JSONValue {
357357
switch self {
358358
case .mutate(let status, let body), .query(let status, let body):
359359
var o: [String: JSONValue] = [

‎swift/Sources/Zero/Client/Definitions.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ public enum MutationOutcome: Sendable, Hashable {
271271
case zeroError(message: String)
272272

273273
/// The JS result object.
274-
public var json: JSONValue {
274+
package var json: JSONValue {
275275
switch self {
276276
case .success(let data):
277277
var o: [String: JSONValue] = ["type": "success"]

‎swift/Sources/Zero/Client/Engine.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public struct UpdateNeededReason: Sendable, Hashable {
6262
public var type: String
6363
public var message: String?
6464

65-
public var json: JSONValue {
65+
package var json: JSONValue {
6666
var o: [String: JSONValue] = ["type": .string(type)]
6767
if let message { o["message"] = .string(message) }
6868
return .object(o)

‎swift/Sources/Zero/Client/Keys.swift‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
/// Storage key layout (`packages/zero-client/src/client/keys.ts`).
2-
public enum Keys {
3-
public static let desiredQueriesPrefix = "d/"
4-
public static let gotQueriesPrefix = "g/"
5-
public static let entitiesPrefix = "e/"
6-
public static let mutationsPrefix = "m/"
2+
package enum Keys {
3+
package static let desiredQueriesPrefix = "d/"
4+
package static let gotQueriesPrefix = "g/"
5+
package static let entitiesPrefix = "e/"
6+
package static let mutationsPrefix = "m/"
77

8-
public struct InvalidPrimaryKeyValue: Error, CustomStringConvertible {
9-
public let column: String
10-
public let value: JSONValue?
11-
public var description: String {
8+
package struct InvalidPrimaryKeyValue: Error, CustomStringConvertible {
9+
package let column: String
10+
package let value: JSONValue?
11+
package var description: String {
1212
"Invalid primary key value for \(column): \(value.map(JSONSerializer.stringify) ?? "undefined")"
1313
}
1414
}
1515

1616
/// `toPrimaryKeyString`: `e/<table>/<id>`. A single-column key is the value
1717
/// as JS `String()` prints it; a compound key is the decimal `h128` of the
1818
/// JSON array of its values.
19-
public static func primaryKeyString(
19+
package static func primaryKeyString(
2020
table: String, primaryKey: [String], row: Row
2121
) throws(InvalidPrimaryKeyValue) -> String {
2222
func scalar(_ column: String) throws(InvalidPrimaryKeyValue) -> JSONValue {
@@ -47,15 +47,15 @@ public enum Keys {
4747
}
4848
}
4949

50-
public static func desiredQueriesKey(clientID: String, hash: String) -> String {
50+
package static func desiredQueriesKey(clientID: String, hash: String) -> String {
5151
desiredQueriesPrefix + clientID + "/" + hash
5252
}
5353

54-
public static func gotQueriesKey(hash: String) -> String {
54+
package static func gotQueriesKey(hash: String) -> String {
5555
gotQueriesPrefix + hash
5656
}
5757

58-
public static func mutationResponseKey(clientID: String, id: Double) -> String {
58+
package static func mutationResponseKey(clientID: String, id: Double) -> String {
5959
mutationsPrefix + clientID + "/" + JSNumber.format(id)
6060
}
6161
}

0 commit comments

Comments
 (0)