-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #862 from microsoft/feature/backing-store
Restore backing store support
- Loading branch information
Showing
12 changed files
with
369 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { BackingStoreFactorySingleton } from "./backingStoreFactorySingleton"; | ||
|
||
// A method that creates a ProxyHandler for a generic model T and attaches it to a backing store. | ||
export function createBackedModelProxyHandler<T extends {}>(): ProxyHandler<T> { | ||
|
||
// Each model has a backing store that is created by the BackingStoreFactorySingleton | ||
const backingStore = BackingStoreFactorySingleton.instance.createBackingStore(); | ||
|
||
/** | ||
* The ProxyHandler for the model. | ||
*/ | ||
const handler: ProxyHandler<T> = { | ||
get(target, prop, receiver) { | ||
console.debug(`BackingStore - Getting property '${prop.toString()}' from backing store`); | ||
if (prop === 'backingStore') { | ||
return backingStore; | ||
} | ||
return backingStore.get(prop.toString()); | ||
}, | ||
set(target, prop, value, receiver) { | ||
if (prop === 'backingStore') { | ||
console.warn(`BackingStore - Ignoring attempt to set 'backingStore' property`); | ||
return true; | ||
} | ||
// set the value on the target object as well to allow it to have keys needed for serialization/deserialization | ||
Reflect.set(target, prop, value, receiver); | ||
console.debug(`BackingStore - Setting property '${prop.toString()}'`); | ||
backingStore.set(prop.toString(), value); | ||
return true; | ||
}, | ||
}; | ||
return handler; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { type ParseNode } from "../serialization"; | ||
|
||
export const BackingStoreKey = "backingStoreEnabled"; | ||
|
||
/** | ||
* Check if the object is an instance a BackedModel | ||
* @param obj | ||
* @returns | ||
*/ | ||
export function isBackingStoreEnabled(fields: Record<string, (node: ParseNode) => void> ): boolean { | ||
// Check if the fields contain the backing store key | ||
return Object.keys(fields).includes(BackingStoreKey); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 86 additions & 0 deletions
86
packages/abstractions/test/common/store/backedModelProxyTest.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { type BackedModel, type BackingStore, BackingStoreFactorySingleton, createBackedModelProxyHandler } from "../../../src/store"; | ||
import { assert } from "chai"; | ||
|
||
export interface Model extends BackedModel { | ||
name?: string; | ||
age?: number; | ||
} | ||
|
||
describe('createBackedModelProxyHandler', () => { | ||
let backingStoreFactorySingleton: BackingStoreFactorySingleton; | ||
const fakeBackingStore = {} as BackingStore; | ||
|
||
beforeEach(() => { | ||
backingStoreFactorySingleton = BackingStoreFactorySingleton.instance; | ||
}); | ||
|
||
afterEach(() => { | ||
// Reset the backing store factory if required | ||
}); | ||
|
||
it('should get a property from the backing store', () => { | ||
// Arrange | ||
const handler = createBackedModelProxyHandler<Model>(); | ||
const model = new Proxy<Model>({backingStore: fakeBackingStore}, handler); | ||
|
||
// Act | ||
model.backingStore?.set("name", "Bob"); | ||
|
||
// Assert | ||
assert.equal(model.backingStore?.get("name"), 'Bob'); | ||
}); | ||
|
||
it('should set a property in the backing store', () => { | ||
// Arrange | ||
const handler = createBackedModelProxyHandler<{name?: string}>(); | ||
const model = new Proxy<Model>({backingStore: fakeBackingStore}, handler); | ||
|
||
// Act | ||
model.name = 'Bob'; | ||
|
||
// Assert | ||
assert.equal(model.backingStore?.get("name"), 'Bob'); | ||
}); | ||
|
||
it('should get and set multiple properties in the backing store', () => { | ||
// Arrange | ||
const handler = createBackedModelProxyHandler(); | ||
const model = new Proxy<Model>({backingStore: fakeBackingStore}, handler); | ||
|
||
// Act | ||
model.name = 'Bob'; | ||
model.age = 30; | ||
const name = model.name; | ||
const age = model.age; | ||
|
||
// Assert | ||
assert.equal(model.backingStore?.get("name"), name); | ||
assert.equal(model.backingStore?.get("age"), age); | ||
}); | ||
|
||
it('should ignore setting the backingStore property', () => { | ||
// Arrange | ||
const handler = createBackedModelProxyHandler(); | ||
const model = new Proxy<Model>({backingStore: fakeBackingStore}, handler); | ||
|
||
// Act | ||
const dummyBackingStore = {} as BackingStore; | ||
model.backingStore = dummyBackingStore; | ||
|
||
// Assert | ||
assert.notEqual(model.backingStore, dummyBackingStore); | ||
}); | ||
|
||
it('should return the backing store when the property itself is backingStore', () => { | ||
// Arrange | ||
const handler = createBackedModelProxyHandler(); | ||
const model = new Proxy<Model>({backingStore: fakeBackingStore}, handler); | ||
|
||
// Act | ||
const backingStore = model.backingStore; | ||
|
||
// Assert | ||
assert.isDefined(model.backingStore); | ||
assert.notEqual(model.backingStore, fakeBackingStore); | ||
}); | ||
}); |
18 changes: 18 additions & 0 deletions
18
packages/abstractions/test/common/store/backingStoreUtilsTest.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { assert } from "chai"; | ||
import { isBackingStoreEnabled } from "../../../src/store/backingStoreUtils"; | ||
import { type TestBackedModel, createTestBackedModelFromDiscriminatorValue, createTestParserFromDiscriminatorValue } from "./testEntity"; | ||
import { type ParseNode } from "../../../src"; | ||
|
||
it("Test backing store should be enabled if the parsableFactory has backingStore property", async () => { | ||
const testBackedModel = {} as TestBackedModel; | ||
const fields = createTestBackedModelFromDiscriminatorValue({} as ParseNode)(testBackedModel); | ||
const backingStoreEnabled = isBackingStoreEnabled(fields); | ||
assert.isTrue(backingStoreEnabled); | ||
}); | ||
|
||
it("Test backing store should not be enabled if the parsableFactory lacks backingStore property", async () => { | ||
const testModel = {} as TestBackedModel; | ||
const fields = createTestParserFromDiscriminatorValue({} as ParseNode)(testModel); | ||
const backingStoreEnabled = isBackingStoreEnabled(fields); | ||
assert.isFalse(backingStoreEnabled); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import type { BackedModel, BackingStore, Parsable, ParseNode } from "../../../src"; | ||
|
||
const fakeBackingStore: BackingStore = {} as BackingStore; | ||
|
||
export interface TestParser { | ||
testString?: string | undefined; | ||
foos?: FooResponse[] | undefined; | ||
} | ||
export interface TestBackedModel extends TestParser, BackedModel { | ||
backingStoreEnabled?: boolean | undefined; | ||
} | ||
export interface FooResponse extends Parsable { | ||
id?: string | undefined; | ||
bars?: BarResponse[] | undefined; | ||
} | ||
export interface BarResponse extends Parsable { | ||
propA?: string | undefined; | ||
propB?: string | undefined; | ||
propC?: Date | undefined; | ||
} | ||
|
||
export function createTestParserFromDiscriminatorValue( | ||
parseNode: ParseNode | undefined | ||
) { | ||
if (!parseNode) throw new Error("parseNode cannot be undefined"); | ||
return deserializeTestParser; | ||
} | ||
|
||
export function createTestBackedModelFromDiscriminatorValue( | ||
parseNode: ParseNode | undefined | ||
) { | ||
if (!parseNode) throw new Error("parseNode cannot be undefined"); | ||
return deserializeTestBackedModel; | ||
} | ||
|
||
export function createFooParserFromDiscriminatorValue( | ||
parseNode: ParseNode | undefined | ||
) { | ||
if (!parseNode) throw new Error("parseNode cannot be undefined"); | ||
return deserializeFooParser; | ||
} | ||
|
||
export function createBarParserFromDiscriminatorValue( | ||
parseNode: ParseNode | undefined | ||
) { | ||
if (!parseNode) throw new Error("parseNode cannot be undefined"); | ||
return deserializeBarParser; | ||
} | ||
|
||
export function deserializeTestParser( | ||
testParser: TestParser | undefined = {} | ||
): Record<string, (node: ParseNode) => void> { | ||
return { | ||
foos: (n) => { | ||
testParser.foos = n.getCollectionOfObjectValues(createFooParserFromDiscriminatorValue); | ||
} | ||
}; | ||
} | ||
|
||
export function deserializeTestBackedModel( | ||
testParser: TestBackedModel | undefined = {} | ||
): Record<string, (node: ParseNode) => void> { | ||
return { | ||
backingStoreEnabled: (n) => { | ||
testParser.backingStoreEnabled = true; | ||
}, | ||
foos: (n) => { | ||
testParser.foos = n.getCollectionOfObjectValues(createFooParserFromDiscriminatorValue); | ||
} | ||
}; | ||
} | ||
|
||
export function deserializeFooParser( | ||
fooResponse: FooResponse | undefined = {} | ||
): Record<string, (node: ParseNode) => void> { | ||
return { | ||
id: (n) => { | ||
fooResponse.id = n.getStringValue(); | ||
}, | ||
bars: (n) => { | ||
fooResponse.bars = n.getCollectionOfObjectValues(createBarParserFromDiscriminatorValue); | ||
} | ||
}; | ||
} | ||
|
||
export function deserializeBarParser( | ||
barResponse: BarResponse | undefined = {} | ||
): Record<string, (node: ParseNode) => void> { | ||
return { | ||
propA: (n) => { | ||
barResponse.propA = n.getStringValue(); | ||
}, | ||
propB: (n) => { | ||
barResponse.propB = n.getStringValue(); | ||
}, | ||
propC: (n) => { | ||
barResponse.propC = n.getDateValue(); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.