Skip to content

Commit

Permalink
* [FEAT identifiers] adds ts interfaces for identifiers work
Browse files Browse the repository at this point in the history
* [FEAT identifiers] adds uuid-v4 util
* [FEAT identifiers] adds ts-interface for json-api concepts w/ember-data flavor
* [FEAT identifier] adds toString to interface for even more debuggability
* [FEAT identifier] makes debug members of the interface optional
* [FEAT IdentifierCache] implements the cache to spec
* record-data-wrapper -> store-wrapper.js (fix name)
* fix store usage by relationships without leaking to RecordData
  • Loading branch information
runspired committed Mar 24, 2019
1 parent 739f78e commit a37bf3e
Show file tree
Hide file tree
Showing 36 changed files with 1,008 additions and 558 deletions.
423 changes: 423 additions & 0 deletions addon/-private/identifiers/cache.ts

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions addon/-private/identifiers/utils/uuid-v4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// support IE11
declare global {
interface Window {
msCrypto: Crypto;
}
}

const CRYPTO = typeof window !== 'undefined' && window.msCrypto && typeof window.msCrypto.getRandomValues === 'function' ? window.msCrypto : crypto;

// we might be able to optimize this by requesting more bytes than we need at a time
function rng() {
// WHATWG crypto RNG - http://wiki.whatwg.org/wiki/Crypto
let rnds8 = new Uint8Array(16);

return CRYPTO.getRandomValues(rnds8);
}

/**
* Convert array of 16 byte values to UUID string format of the form:
* XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
*/
const byteToHex: string[] = [];
for (let i = 0; i < 256; ++i) {
byteToHex[i] = (i + 0x100).toString(16).substr(1);
}

function bytesToUuid(buf) {
let bth = byteToHex;
// join used to fix memory issue caused by concatenation: https://bugs.chromium.org/p/v8/issues/detail?id=3175#c4
return ([bth[buf[0]], bth[buf[1]],
bth[buf[2]], bth[buf[3]], '-',
bth[buf[4]], bth[buf[5]], '-',
bth[buf[6]], bth[buf[7]], '-',
bth[buf[8]], bth[buf[9]], '-',
bth[buf[10]], bth[buf[11]],
bth[buf[12]], bth[buf[13]],
bth[buf[14]], bth[buf[15]]]).join('');
}

export default function uuidv4(): string {
let rnds = rng();

// Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
rnds[6] = (rnds[6] & 0x0f) | 0x40;
rnds[8] = (rnds[8] & 0x3f) | 0x80;

return bytesToUuid(rnds);
}
128 changes: 128 additions & 0 deletions addon/-private/internal-model/internal-model-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { Dict } from '../ts-interfaces/utils';
import { StableRecordIdentifier } from '../ts-interfaces/identifier';
import IdentifierCache from '../identifiers/cache';
import InternalModel from '../system/model/internal-model';
import { assert } from '@ember/debug';
import { NewResourceObject, ExistingResourceIdentifierObject } from '../ts-interfaces/json-api';

export default class InternalModelCache {
private _identifierCache: IdentifierCache;
private _types: Dict<string, InternalModel[]> = Object.create(null);
private _lids: Dict<string, InternalModel> = Object.create(null)
private _store;

constructor(store, identifierCache) {
this._identifierCache = identifierCache;
this._store = store;
}

all(modelName: string) {
let all = this._types[modelName] = this._types[modelName] || [];
return all;
}

get(identifier: StableRecordIdentifier): InternalModel | null {
return this._lids[identifier.lid] || null;
}

private _set(identifier: StableRecordIdentifier, internalModel: InternalModel): void {
this._lids[identifier.lid] = internalModel;
this.all(identifier.type).push(internalModel);
}

createInternalModelForNewRecord(data: NewResourceObject): InternalModel {
// check for an existing identifier
let identifier;
let internalModel;

if (data.id !== null) {
identifier = this._identifierCache.peekRecordIdentifier(data as ExistingResourceIdentifierObject, false);
internalModel = identifier !== null ? this.get(identifier) : null;
}

if (internalModel && internalModel.hasScheduledDestroy()) {
// unloadRecord is async, if one attempts to unload + then sync create,
// we must ensure the unload is complete before starting the create
// The push path will utilize _getOrCreateInternalModelFor()
// which will call `cancelDestroy` instead for this unload + then
// sync push scenario. Once we have true client-side
// delete signaling, we should never call destroySync
internalModel.destroySync();
internalModel = null;
this._identifierCache.forgetRecordIdentifier(identifier);
}

assert(
`The id ${identifier.id} has already been used with another record for modelClass '${
identifier.type
}'.`,
!internalModel
);

identifier = this._identifierCache.createIdentifierForNewRecord({
type: data.type,
id: data.id
});

internalModel = new InternalModel(this._store, identifier);
this._set(identifier, internalModel);

return internalModel;
}

ensureInstance(identifier: StableRecordIdentifier): InternalModel {
let internalModel = this.get(identifier);

if (internalModel !== null) {
// unloadRecord is async, if one attempts to unload + then sync push,
// we must ensure the unload is canceled before continuing
// The createRecord path will utilize _createInternalModel() directly
// which will call `destroySync` instead for this unload + then
// sync createRecord scenario. Once we have true client-side
// delete signaling, we should never call destroySync
if (internalModel.hasScheduledDestroy()) {
internalModel.cancelDestroy();
}

return internalModel;
}

internalModel = new InternalModel(this._store, identifier);
this._set(identifier, internalModel);

return internalModel;
}

clear(type?: string) {
let cached: InternalModel[] = [];

if (type !== undefined) {
let all = this.all(type);
this._types[type] = []; // clear it
cached.push(...all);
} else {
Object.keys(this._types).forEach(type => {
cached.push(...this.all(type));
this._types[type] = []; // clear it
});
}

for (let i = 0; i < cached.length; i++) {
let internalModel = cached[i];

// this then calls "remove"
// but only once the time is right
internalModel.unloadRecord();
}
}

remove(identifier: StableRecordIdentifier) {
let internalModel = this._lids[identifier.lid];
delete this._lids[identifier.lid];
let all = this.all(identifier.type);
let index = all.indexOf(internalModel);
if (index !== -1) {
all.splice(index, 1);
}
}
}
2 changes: 1 addition & 1 deletion addon/-private/system/coerce-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
type Coercable = string | number | boolean | null | undefined | symbol;

function coerceId(id: number | boolean | symbol): string;
function coerceId(id: null | undefined | ''): null;
function coerceId(id: null | undefined | string): null;
function coerceId(id: string): string | null;
function coerceId(id: Coercable): string | null {
if (id === null || id === undefined || id === '') {
Expand Down
48 changes: 0 additions & 48 deletions addon/-private/system/identity-map.ts

This file was deleted.

126 changes: 0 additions & 126 deletions addon/-private/system/internal-model-map.ts

This file was deleted.

Loading

0 comments on commit a37bf3e

Please sign in to comment.