|
| 1 | +import { assert } from '@ember/debug'; |
| 2 | + |
| 3 | +import { recordIdentifierFor, storeFor, type StoreRequestInput } from '@ember-data/store'; |
| 4 | +import type { InstanceCache } from '@ember-data/store/-private/caches/instance-cache'; |
| 5 | +import type { StableRecordIdentifier } from '@warp-drive/core-types'; |
| 6 | +import type { Cache } from '@warp-drive/core-types/cache'; |
| 7 | +import { SkipCache } from '@warp-drive/core-types/request'; |
| 8 | + |
| 9 | +export type SaveRecordRequestInput = StoreRequestInput & { |
| 10 | + op: 'createRecord' | 'deleteRecord' | 'updateRecord'; |
| 11 | + data: { |
| 12 | + record: StableRecordIdentifier; |
| 13 | + options: SaveRecordBuilderOptions; |
| 14 | + }; |
| 15 | + records: [StableRecordIdentifier]; |
| 16 | +}; |
| 17 | + |
| 18 | +export type SaveRecordBuilderOptions = Record<string, unknown>; |
| 19 | + |
| 20 | +function _resourceIsFullDeleted(identifier: StableRecordIdentifier, cache: Cache): boolean { |
| 21 | + return cache.isDeletionCommitted(identifier) || (cache.isNew(identifier) && cache.isDeleted(identifier)); |
| 22 | +} |
| 23 | + |
| 24 | +function resourceIsFullyDeleted(instanceCache: InstanceCache, identifier: StableRecordIdentifier): boolean { |
| 25 | + const cache = instanceCache.cache; |
| 26 | + return !cache || _resourceIsFullDeleted(identifier, cache); |
| 27 | +} |
| 28 | + |
| 29 | +/** |
| 30 | + * FIXME: Docs |
| 31 | + This function builds a request config for the given type. |
| 32 | + When passed to `store.request`, this config will result in the same behavior as a `store.findAll` request. |
| 33 | + Additionally, it takes the same options as `store.findAll`. |
| 34 | +
|
| 35 | + @since x.x.x |
| 36 | + @method query |
| 37 | + @public |
| 38 | + @param {String} type the name of the resource |
| 39 | + @param {object} query a query to be used by the adapter |
| 40 | + @param {SaveRecordBuilderOptions} options optional, may include `adapterOptions` hash which will be passed to adapter.query |
| 41 | + @return {SaveRecordRequestInput} request config |
| 42 | +*/ |
| 43 | +export function saveRecordBuilder<T>(record: T, options: Record<string, unknown> = {}): SaveRecordRequestInput { |
| 44 | + const store = storeFor(record); |
| 45 | + assert(`Unable to initiate save for a record in a disconnected state`, store); |
| 46 | + const identifier = recordIdentifierFor(record); |
| 47 | + |
| 48 | + if (!identifier) { |
| 49 | + // this commonly means we're disconnected |
| 50 | + // but just in case we throw here to prevent bad things. |
| 51 | + throw new Error(`Record Is Disconnected`); |
| 52 | + } |
| 53 | + // TODO we used to check if the record was destroyed here |
| 54 | + assert( |
| 55 | + `Cannot initiate a save request for an unloaded record: ${identifier.lid}`, |
| 56 | + store._instanceCache.recordIsLoaded(identifier) |
| 57 | + ); |
| 58 | + if (resourceIsFullyDeleted(store._instanceCache, identifier)) { |
| 59 | + throw new Error('cannot build saveRecord request for deleted record'); |
| 60 | + } |
| 61 | + |
| 62 | + if (!options) { |
| 63 | + options = {}; |
| 64 | + } |
| 65 | + let operation: 'createRecord' | 'deleteRecord' | 'updateRecord' = 'updateRecord'; |
| 66 | + |
| 67 | + const cache = store.cache; |
| 68 | + if (cache.isNew(identifier)) { |
| 69 | + operation = 'createRecord'; |
| 70 | + } else if (cache.isDeleted(identifier)) { |
| 71 | + operation = 'deleteRecord'; |
| 72 | + } |
| 73 | + |
| 74 | + return { |
| 75 | + op: operation, |
| 76 | + data: { |
| 77 | + options, |
| 78 | + record: identifier, |
| 79 | + }, |
| 80 | + records: [identifier], |
| 81 | + cacheOptions: { [SkipCache as symbol]: true }, |
| 82 | + }; |
| 83 | +} |
| 84 | + |
| 85 | +/* |
| 86 | +
|
| 87 | +TODO: |
| 88 | +* [] test this |
| 89 | +* [] make sure nothing fails bc of willCommit change |
| 90 | +
|
| 91 | +*/ |
0 commit comments