@@ -23,6 +23,8 @@ public actor Datastore<
2323 let directIndexes : [ IndexPath < CodedType > ]
2424 let computedIndexes : [ IndexPath < CodedType > ]
2525
26+ var updatedDescriptor : DatastoreDescriptor ?
27+
2628 fileprivate var warmupStatus : TaskStatus = . waiting
2729 fileprivate var warmupProgressHandlers : [ ProgressHandler ] = [ ]
2830
@@ -74,6 +76,33 @@ public actor Datastore<
7476 }
7577}
7678
79+ // MARK: - Helper Methods
80+
81+ extension Datastore {
82+ func updatedDescriptor( for instance: CodedType ) throws -> DatastoreDescriptor {
83+ if let updatedDescriptor {
84+ return updatedDescriptor
85+ }
86+
87+ let descriptor = try DatastoreDescriptor (
88+ version: version,
89+ sampleInstance: instance,
90+ identifierType: IdentifierType . self,
91+ directIndexes: directIndexes,
92+ computedIndexes: computedIndexes
93+ )
94+ updatedDescriptor = descriptor
95+ return descriptor
96+ }
97+
98+ func decoder( for version: Version ) throws -> ( _ data: Data ) async throws -> CodedType {
99+ guard let decoder = decoders [ version] else {
100+ throw DatastoreError . missingDecoder ( version: String ( describing: version) )
101+ }
102+ return decoder
103+ }
104+ }
105+
77106// MARK: - Warmup
78107
79108extension Datastore {
@@ -242,11 +271,193 @@ extension Datastore {
242271 }
243272}
244273
245- // MARK: - Writting
274+ // MARK: - Writing
246275
247276extension Datastore where AccessMode == ReadWrite {
277+ /// Persist an instance for a given identifier.
278+ ///
279+ /// - Note: If you instance conforms to Identifiable, it it preferable to use ``persist(_:)`` instead.
248280 public func persist( _ instance: CodedType , to idenfifier: IdentifierType ) async throws {
281+ try await warmupIfNeeded ( )
282+
283+ // TODO: Start an implicit transaction
284+
285+ /// Create any missing indexes or prime the datastore for writing.
286+ let updatedDescriptor = try updatedDescriptor ( for: instance)
287+ try await persistence. apply ( descriptor: updatedDescriptor, for: key)
288+
289+ let versionData = try Data ( version)
290+ let instanceData = try await encoder ( instance)
291+
292+ let existingEntry : ( cursor: any InstanceCursorProtocol , instance: CodedType ) ? = try await {
293+ do {
294+ let existingEntry = try await persistence. primaryIndexCursor ( for: idenfifier, datastoreKey: key)
295+
296+ let existingVersion = try Version ( existingEntry. versionData)
297+ let decoder = try decoder ( for: existingVersion)
298+ let existingInstance = try await decoder ( existingEntry. instanceData)
299+
300+ return ( cursor: existingEntry. cursor, instance: existingInstance)
301+ } catch PersistenceError . instanceNotFound {
302+ return nil
303+ } catch {
304+ throw error
305+ }
306+ } ( )
307+
308+ /// Grab the insertion cursor in the primary index.
309+ let existingInstance = existingEntry? . instance
310+ let insertionCursor : any InsertionCursorProtocol = try await {
311+ if let existingEntry { return existingEntry. cursor }
312+ return try await persistence. primaryIndexCursor ( inserting: idenfifier, datastoreKey: key)
313+ } ( )
314+
315+ /// Persist the entry in the primary index
316+ try await persistence. persistPrimaryIndexEntry (
317+ versionData: versionData,
318+ identifierValue: idenfifier,
319+ instanceData: instanceData,
320+ cursor: insertionCursor,
321+ datastoreKey: key
322+ )
323+
324+ var queriedIndexes : Set < String > = [ ]
325+
326+ /// Persist the direct indexes with full copies
327+ for indexPath in directIndexes {
328+ let indexName = indexPath. path
329+ guard !queriedIndexes. contains ( indexName) else { continue }
330+ queriedIndexes. insert ( indexName)
331+
332+ let existingValue = existingInstance ? [ keyPath: indexPath]
333+ let updatedValue = instance [ keyPath: indexPath]
334+ // let indexType = updatedValue.indexedType
335+
336+ if let existingValue {
337+ /// Grab a cursor to the old value in the index.
338+ let existingValueCursor = try await persistence. directIndexCursor (
339+ for: existingValue. indexed,
340+ identifier: idenfifier,
341+ indexName: indexName,
342+ datastoreKey: key
343+ )
344+
345+ /// Delete it.
346+ try await persistence. deleteDirectIndexEntry (
347+ cursor: existingValueCursor. cursor,
348+ indexName: indexName,
349+ datastoreKey: key
350+ )
351+ }
352+
353+ /// Grab a cursor to insert the new value in the index.
354+ let updatedValueCursor = try await persistence. directIndexCursor (
355+ inserting: updatedValue. indexed,
356+ identifier: idenfifier,
357+ indexName: indexName,
358+ datastoreKey: key
359+ )
360+
361+ /// Insert it.
362+ try await persistence. persistDirectIndexEntry (
363+ versionData: versionData,
364+ indexValue: updatedValue. indexed,
365+ identifierValue: idenfifier,
366+ instanceData: instanceData,
367+ cursor: updatedValueCursor,
368+ indexName: indexName,
369+ datastoreKey: key
370+ )
371+ }
372+
373+ /// Next, go through any remaining computed indexes as secondary indexes.
374+ for indexPath in computedIndexes {
375+ let indexName = indexPath. path
376+ guard !queriedIndexes. contains ( indexName) else { continue }
377+ queriedIndexes. insert ( indexName)
378+
379+ let existingValue = existingInstance ? [ keyPath: indexPath]
380+ let updatedValue = instance [ keyPath: indexPath]
381+ // let indexType = updatedValue.indexedType
382+
383+ if let existingValue {
384+ /// Grab a cursor to the old value in the index.
385+ let existingValueCursor = try await persistence. secondaryIndexCursor (
386+ for: existingValue. indexed,
387+ identifier: idenfifier,
388+ indexName: indexName,
389+ datastoreKey: key
390+ )
391+
392+ /// Delete it.
393+ try await persistence. deleteSecondaryIndexEntry (
394+ cursor: existingValueCursor,
395+ indexName: indexName,
396+ datastoreKey: key
397+ )
398+ }
399+
400+ /// Grab a cursor to insert the new value in the index.
401+ let updatedValueCursor = try await persistence. secondaryIndexCursor (
402+ inserting: updatedValue. indexed,
403+ identifier: idenfifier,
404+ indexName: indexName,
405+ datastoreKey: key
406+ )
407+
408+ /// Insert it.
409+ try await persistence. persistSecondaryIndexEntry (
410+ indexValue: updatedValue. indexed,
411+ identifierValue: idenfifier,
412+ cursor: updatedValueCursor,
413+ indexName: indexName,
414+ datastoreKey: key
415+ )
416+ }
417+
418+ /// Remove any remaining indexed values from the old instance.
419+ try await Mirror . indexedChildren ( from: existingInstance) { indexName, value in
420+ guard !queriedIndexes. contains ( indexName) else { return }
421+
422+ /// Grab a cursor to the old value in the index.
423+ let existingValueCursor = try await persistence. secondaryIndexCursor (
424+ for: value,
425+ identifier: idenfifier,
426+ indexName: indexName,
427+ datastoreKey: key
428+ )
429+
430+ /// Delete it.
431+ try await persistence. deleteSecondaryIndexEntry (
432+ cursor: existingValueCursor,
433+ indexName: indexName,
434+ datastoreKey: key
435+ )
436+ }
437+
438+ /// Re-insert those indexes from the new index.
439+ try await Mirror . indexedChildren ( from: instance, assertIdentifiable: true ) { indexName, value in
440+ guard !queriedIndexes. contains ( indexName) else { return }
441+
442+ /// Grab a cursor to insert the new value in the index.
443+ let updatedValueCursor = try await persistence. secondaryIndexCursor (
444+ inserting: value,
445+ identifier: idenfifier,
446+ indexName: indexName,
447+ datastoreKey: key
448+ )
449+
450+ /// Insert it.
451+ try await persistence. persistSecondaryIndexEntry (
452+ indexValue: value,
453+ identifierValue: idenfifier,
454+ cursor: updatedValueCursor,
455+ indexName: indexName,
456+ datastoreKey: key
457+ )
458+ }
249459
460+ // TODO: Close implicit transaction
250461 }
251462
252463 public func delete( _ idenfifier: IdentifierType ) async throws {
0 commit comments