Skip to content

Commit

Permalink
Merge pull request #3 from RougeWare/feature/renaming/COID
Browse files Browse the repository at this point in the history
Grand Renaming to COID
  • Loading branch information
KyNorthstar authored May 16, 2024
2 parents da94696 + 6f895f8 commit ede27fa
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 185 deletions.
13 changes: 8 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
import PackageDescription

let package = Package(
name: "AppUniqueIdentifier",
name: "ContextuallyUniqueIdentifier",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "COID",
targets: ["COID"]),
.library(
name: "AppUniqueIdentifier",
targets: ["AppUniqueIdentifier"]),
targets: ["COID"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
Expand All @@ -19,12 +22,12 @@ let package = Package(
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "AppUniqueIdentifier",
name: "COID",
dependencies: [
.product(name: "SimpleLogging", package: "Swift-Simple-Logging"),
]),
.testTarget(
name: "AppUniqueIdentifierTests",
dependencies: ["AppUniqueIdentifier"]),
name: "ContextuallyUniqueIdentifierTests",
dependencies: ["COID"]),
]
)
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# `AppUniqueIdentifier`
# `ContextuallyUniqueIdentifier`

We call these COIDs because [CUID was already taken](https://github.com/paralleldrive/cuid2).

An AUID is an identifier which is unique to a runtime andor save data. While UUID is good for general use, this is good specifically for a single session.
A COID is an identifier which is unique to a runtime andor save data. While things like [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier), [CUID](https://github.com/paralleldrive/cuid2), [ULID](https://github.com/ulid/spec), [NanoID](https://github.com/ai/nanoid), and their ilk are all good for general use, this is good specifically for a single context. Be that an app, a runtime, a session, a save file, a user, etc. You choose the context where this is relevant

The philosophy behind this comes from a desire to have a unique identifier assigned to various objects, but use as little data as possible when serialized. In fact, this was invented for [Rent Split](https://split.rent), a webapp which stored no data at all server-side, so the share URLs contained the entire app state; that's why the IDs needed to be as short as possible.
The philosophy behind this comes from a desire to have a unique identifier assigned to various objects, but **use as little data** as possible when serialized. In fact, this was invented for [Rent Split](https://split.rent), a webapp which initially stored no data at all locally, so the share URLs contained the entire app state; that's why the IDs needed to be as short as possible.

This is only guaranteed to be unique per-session if used correctly. Any further uniqueness is not guaranteed. You might think of these as Instance-Unique Identifiers if that helps.
This is **only guaranteed to be unique per-context** if used correctly. Any further uniqueness is not guaranteed. You might think of these as Instance-Unique Identifiers if that helps.

The current implementation simply uses positive integers. However, it's good practice to treat this as an opaque type in case the implementation changes in the future.

Expand All @@ -17,7 +18,7 @@ To create a new ID, you must call `.next()`, for a new ID to be generated and re

```swift
struct Person: Identifiable, Codable {
var id: AppUniqueIdentifier = .next()
var id: COID = .next()
let name: String
}

Expand All @@ -29,7 +30,7 @@ let person = Person(name: "Dax")

## Encoding & Decoding an ID

AUIDs are currently encoded as simple integers. One can expect the above `Person` instance to be encoded like this:
COIDs are currently encoded as simple integers. One can expect the above `Person` instance to be encoded like this:

```json
{
Expand All @@ -39,7 +40,7 @@ AUIDs are currently encoded as simple integers. One can expect the above `Person
```


AUID has built-in encoding and decoding, so nothing special need be done. Just use any Swift encoder/decoder, like this:
COID has built-in encoding and decoding, so nothing special need be done. Just use any Swift encoder/decoder, like this:

```swift
let jsonData = try JSONEncoder().encode(person)
Expand All @@ -48,7 +49,7 @@ assert(person == decodedPerson)
```


The act of decoding an AUID automatically assures that the ID is not used in any future calls to `.next()`. Encoding an AUID, though, has no special behavior.
The act of decoding an COID automatically assures that the ID is not used in any future calls to `.next()`. Encoding an COID, though, has no special behavior.



Expand All @@ -61,18 +62,18 @@ Text(verbatim: person.id.description)
```


If you have a string form of an ID already (e.g. one you got from calling `.description`), you may transform it back into an `AppUniqueIdentifier` with the `.init(_:)` initializer. Only valid ID strings will result in a new value; invalid ones result in `nil`. The subsystem will ensure that this ID is not returned by `.next()`
If you have a string form of an ID already (e.g. one you got from calling `.description`), you may transform it back into an `ContextuallyUniqueIdentifier` with the `.init(_:)` initializer. Only valid ID strings will result in a new value; invalid ones result in `nil`. The subsystem will ensure that this ID is not returned by `.next()`



## When you're done with an ID

If you no longer wish to use an ID (for example, the concept it was tracking has been deleted by the user), then you may pass it to `AppUniqueIdentifier.recycle(id:)`:
If you no longer wish to use an ID (for example, the concept it was tracking has been deleted by the user), then you may pass it to `ContextuallyUniqueIdentifier.recycle(id:)`:

```swift
mutating func remove(_ person: Person) {
self.people.remove(person)
AppUniqueIdentifier.recycle(id: person.id)
COID.recycle(id: person.id)
}
```

Expand All @@ -83,7 +84,7 @@ mutating func remove(_ person: Person) {
Since version 1.1, App-Unique Identifiers are arranged into four groups, called "Regions":

1.**General-use** – A large amount of IDs which can be generated and registered. Though finite, it is a large enough range that client-side applications should not exceed its limit if using these APIs correctly. **If your application is using enough IDs that this is too few, then this is not the package for you. I recommend using UUIDs insted.** But seriously, if you use up all these IDs, that's Exbibytes of data; don't worry about it.
2. 🔒 **Unused** – A large amount of IDs which have not been allocated for any use. These cannot be used in any way. Future versions of AUID might introduce usage of these. This blockage is to allow future changes to be nondestructive and backwards-compatible
2. 🔒 **Unused** – A large amount of IDs which have not been allocated for any use. These cannot be used in any way. Future versions of COID might introduce usage of these. This blockage is to allow future changes to be nondestructive and backwards-compatible
3. *️⃣ **Private-Use** – A small amount of IDs which are manually and specifically requested. Like the Unicode private-use plane, this region of IDs has no specific intent/meaning, and allows the developer to ascribe specific meanings to each. The small size of this range means that there are no requests to generate a "next" one, and each specific one should be carefully chosen by the developer.
4. ❗️ **Error** – A single ID which signifies that an error has occurred

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// AppUniqueIdentifier.swift
// ContextuallyUniqueIdentifier.swift
//
//
// Created by Ky Leggiero on 2022-07-08.
Expand All @@ -11,13 +11,20 @@ import SimpleLogging



public typealias COID = ContextuallyUniqueIdentifier

@available(*, deprecated, renamed: "COID", message: "In version 1.2.0, AppUniqueIdentifier was renamed to ContextuallyUniqueIdentifier (COID) to clarify its usage")
public typealias AppUniqueIdentifier = COID



/// An identifier which is unique to this app's runtime.
///
/// This is only guaranteed to be unique in this app's runtime. Any further uniqueness is not guaranteed.
///
/// App-Unique Identifiers are arranged into four groups:
/// 1. ✅ **General-use** – A large amount of IDs which can be generated and registered. Though finite, it is a large enough range that most applications should not exceed its limit if using these APIs correctly. **If your application is using enough IDs that this is too few, then this is not the package for you. I recommend using UUIDs insted.** But seriously, if you use up all these IDs, that's Exbibytes of data; don't worry about it.
/// 2. 🔒 **Unused** – A large amount of IDs which have not been allocated for any use. These cannot be used in any way. Future versions of AUID might introduce usage of these. This blockage is to allow future changes to be nondestructive and backwards-compatible
/// 2. 🔒 **Unused** – A large amount of IDs which have not been allocated for any use. These cannot be used in any way. Future versions of COID might introduce usage of these. This blockage is to allow future changes to be nondestructive and backwards-compatible
/// 3. *️⃣ **Private-Use** – A small amount of IDs which must be manually and specifically requested. Like the Unicode private-use plane, this region of IDs has no specific intent/meaning, and allows the developer to ascribe specific meanings to each. The small size of this range means that there are no requests to generate a "next" one, and each specific one should be carefully chosen by the developer.
/// 4. ❗️ **Error** – A single ID which signifies that an error has occurred
///
Expand All @@ -34,7 +41,7 @@ import SimpleLogging
/// 🔒 🔒 🔒 🔒 🔒 🔒 🔒 🔒 🔒 🔒
/// 🔒 🔒 🔒 🔒 🔒 🔒 🔒 *️⃣ *️⃣ ❗️
/// ```
public struct AppUniqueIdentifier {
public struct ContextuallyUniqueIdentifier {
private let rawValue: ID
}

Expand All @@ -44,7 +51,7 @@ public struct AppUniqueIdentifier {

// MARK: Initialization

fileprivate extension AppUniqueIdentifier {
fileprivate extension ContextuallyUniqueIdentifier {

/// Creates a new app-unique ID with the given value. This also registers it immediately, to ensure all app-unique IDs are unique across all others
///
Expand All @@ -59,7 +66,7 @@ fileprivate extension AppUniqueIdentifier {

// MARK: Static registry

fileprivate extension AppUniqueIdentifier {
fileprivate extension ContextuallyUniqueIdentifier {

/// The IDs which are currently in use in this runtime
private static var __idRegistry = Set<ID>()
Expand Down Expand Up @@ -141,7 +148,7 @@ fileprivate extension AppUniqueIdentifier {

// MARK: - API

public extension AppUniqueIdentifier {
public extension ContextuallyUniqueIdentifier {

/// Finds, registers, and returns the next available ID which is not the same as any currently-existing IDs.
///
Expand Down Expand Up @@ -220,18 +227,22 @@ public extension AppUniqueIdentifier {
///
/// ```
private enum RegionRanges {
static let allPossibleValues: ClosedRange<AppUniqueIdentifier.ID> = .min ... .max
static let allPossibleValues: ClosedRange<ID> = .min ... .max

static let generalUse: Range<ID> = .min ..< .max/2
static let unused: Range<ID> = generalUse.upperBound ..< privateUse.lowerBound
static let privateUse: Range<ID> = .max-ID(UInt8.max) ..< .max

static let error = ID.max


static let generalUse: Range<AppUniqueIdentifier.ID> = .min ..< .max/2
static let unused: Range<AppUniqueIdentifier.ID> = generalUse.upperBound ..< privateUse.lowerBound
static let privateUse: Range<AppUniqueIdentifier.ID> = .max-AppUniqueIdentifier.ID(UInt8.max) ..< .max

static let error = AppUniqueIdentifier.ID.max
typealias ID = COID.ID
}



public extension AppUniqueIdentifier {
public extension ContextuallyUniqueIdentifier {


/// Returns an identifier from the private use region at the given offset
Expand All @@ -247,80 +258,80 @@ public extension AppUniqueIdentifier {
}


/// The error AUID
/// The error COID
///
/// This special value is the only one in the error region, and only means that a serious problem occurred (e.g. could not allocate an AUID).
/// This exists to allow objects to still exist while requiring a non-nil AUID field, even after a serious problem occurred.
/// This special value is the only one in the error region, and only means that a serious problem occurred (e.g. could not allocate an COID).
/// This exists to allow objects to still exist while requiring a non-nil COID field, even after a serious problem occurred.
static let error = Self(rawValue: RegionRanges.error)


/// The region this AUID belongs to. See the documentation for ``AppUniqueIdentifier`` for more information
/// The region this COID belongs to. See the documentation for ``ContextuallyUniqueIdentifier`` for more information
///
/// Each AUID belongs to exactly one region, so this is deterministic for any given AUID.
/// Each COID belongs to exactly one region, so this is deterministic for any given COID.
@inline(__always)
var region: Region {
Region(of: self)
}


/// Determines whether or not this is the error AUID.
/// Determines whether or not this is the error COID.
///
/// The error AUID has no inherent meaning; it just means that a serious problem occurred regarding App-Unique Identifiers
/// The error COID has no inherent meaning; it just means that a serious problem occurred regarding App-Unique Identifiers
@inline(__always)
var isError: Bool {
self == .error
}



/// An AUID region. See the documentation for ``AppUniqueIdentifier`` for more information
/// An COID region. See the documentation for ``ContextuallyUniqueIdentifier`` for more information
enum Region {

/// The General-Use region. See the documentation for ``AppUniqueIdentifier`` for more information
/// The General-Use region. See the documentation for ``ContextuallyUniqueIdentifier`` for more information
case generalUse

/// The unused region. See the documentation for ``AppUniqueIdentifier`` for more information
/// The unused region. See the documentation for ``ContextuallyUniqueIdentifier`` for more information
case unused

/// The Private-Use region. See the documentation for ``AppUniqueIdentifier`` for more information
/// The Private-Use region. See the documentation for ``ContextuallyUniqueIdentifier`` for more information
case privateUse

/// The Error region. See the documentation for ``AppUniqueIdentifier`` for more information
/// The Error region. See the documentation for ``ContextuallyUniqueIdentifier`` for more information
case error
}
}



public extension AppUniqueIdentifier.Region {
public extension ContextuallyUniqueIdentifier.Region {

/// Determines whether this region contains the given identifier.
///
/// Each AUID belongs to exactly one region. So when this returns `true` for one AUID, all other regions return `false`
/// Each COID belongs to exactly one region. So when this returns `true` for one COID, all other regions return `false`
///
/// - Parameter id: Any App-Unique Identifier to check against this range
///
/// - Returns: `true` iff this region contains that ID
func contains(_ id: AppUniqueIdentifier) -> Bool {
func contains(_ id: ContextuallyUniqueIdentifier) -> Bool {
range.contains(id.rawValue)
}
}



extension AppUniqueIdentifier.Region: CaseIterable {
extension ContextuallyUniqueIdentifier.Region: CaseIterable {
}



private extension AppUniqueIdentifier.Region {
private extension ContextuallyUniqueIdentifier.Region {

init(of id: AppUniqueIdentifier) {
init(of id: ContextuallyUniqueIdentifier) {
self = Self.allCases.first { $0.contains(id) } ?? .error
}


var range: any RangeExpression<AppUniqueIdentifier.ID> {
var range: any RangeExpression<ContextuallyUniqueIdentifier.ID> {
switch self {
case .generalUse: RegionRanges.generalUse
case .unused: RegionRanges.unused
Expand All @@ -334,15 +345,15 @@ private extension AppUniqueIdentifier.Region {

// MARK: - Conformance

extension AppUniqueIdentifier: Decodable {
extension ContextuallyUniqueIdentifier: Decodable {
public init(from decoder: Decoder) throws {
self.init(id: try decoder.singleValueContainer().decode(ID.self))
}
}



extension AppUniqueIdentifier: Encodable {
extension ContextuallyUniqueIdentifier: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(id)
Expand All @@ -351,7 +362,7 @@ extension AppUniqueIdentifier: Encodable {



extension AppUniqueIdentifier: LosslessStringConvertible {
extension ContextuallyUniqueIdentifier: LosslessStringConvertible {

public init?(_ description: String) {
guard let id = ID(description) else {
Expand All @@ -368,7 +379,7 @@ extension AppUniqueIdentifier: LosslessStringConvertible {



extension AppUniqueIdentifier: Identifiable {
extension ContextuallyUniqueIdentifier: Identifiable {

@inline(__always)
public var id: ID { rawValue }
Expand All @@ -380,12 +391,12 @@ extension AppUniqueIdentifier: Identifiable {



extension AppUniqueIdentifier: Hashable {}
extension ContextuallyUniqueIdentifier: Hashable {}



extension AppUniqueIdentifier: Comparable {
public static func < (lhs: AppUniqueIdentifier, rhs: AppUniqueIdentifier) -> Bool {
extension ContextuallyUniqueIdentifier: Comparable {
public static func < (lhs: ContextuallyUniqueIdentifier, rhs: ContextuallyUniqueIdentifier) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
Loading

0 comments on commit ede27fa

Please sign in to comment.