@ogre-tools/injectable
Advanced tools
Comparing version 13.2.1 to 14.0.0
@@ -1,1 +0,1 @@ | ||
(()=>{"use strict";var e={d:(t,n)=>{for(var i in n)e.o(n,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:n[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};e.r(t),e.d(t,{createContainer:()=>j,deregistrationCallbackToken:()=>u,getInjectable:()=>d,getInjectionToken:()=>i,injectionDecoratorToken:()=>w,instantiationDecoratorToken:()=>k,isInjectable:()=>I,isInjectionToken:()=>S,lifecycleEnum:()=>a,registrationCallbackToken:()=>p});const n="injection-token",i=({id:e,decorable:t=!0})=>({id:e,aliasType:n,decorable:t}),r=Symbol("non-stored-instance-key"),o=Symbol("stored-instance-key"),a={singleton:{id:"singleton",getInstanceKey:()=>o},keyedSingleton:({getInstanceKey:e})=>({id:"keyedSingleton",getInstanceKey:e}),transient:{id:"transient",getInstanceKey:()=>r}},c="injectable",d=({lifecycle:e=a.singleton,...t})=>({aliasType:c,lifecycle:e,...t}),s=(...e)=>t=>{let n=t;return e.forEach((e=>{n=e(n)})),n},l=require("@ogre-tools/fp"),j=e=>{let t=new Map,n=new Map,i=!1,r=new Set;const o=new Map,a=new Map,c=new Map,s=f({injectableMap:t,injectableIdsByInjectionToken:c}),j=g({injectableMap:t,getInjectablesHavingInjectionToken:s}),k=b({getRelatedInjectables:j}),T=(e,t,n=[x])=>{const i=[...n,{injectable:e}],r=j(e).map((e=>I(e,t,i)));return r.find(l.isPromise)?Promise.all(r):r},m=h({injectMany:T}),I=m(((e,t,c=[])=>{const d=j(e);M(d,e),0===d.length&&!0===e.adHoc?C.register(e):E(d,e,c);const s=k(e);r.add(s.id);const l=n.get(s.id)||s;if(i&&l.causesSideEffects)throw new Error(`Tried to inject "${[...c,{injectable:l}].map((e=>e.injectable.id)).join('" -> "')}" when side-effects are prevented.`);return y({injectable:l,instantiationParameter:t,di:C,instancesByInjectableMap:a,context:c,injectableAndRegistrationContext:o})})),S=m(T),v=e=>{let n=e.id;if(!n)throw new Error("Tried to register injectable without ID.");if(t.has(n))throw new Error(`Tried to register multiple injectables for ID "${n}"`);const i={...e,permitSideEffects:function(){this.causesSideEffects=!1}};if(t.set(i.id,i),a.set(i.id,new Map),e.injectionToken){const t=e.injectionToken.id,i=c.get(t)||new Set;i.add(n),c.set(t,i)}},$=e=>{const t=k(e);a.get(t.id).clear()},P=(e,t)=>{const n=d({id:`${e.id}-decorator-${Math.random()}`,injectionToken:w,decorable:!1,instantiate:()=>({decorate:t,target:e})});v(n)},C={inject:I,injectMany:S,register:(...e)=>{e.forEach((e=>{v(e)}));const t=T(p);e.forEach((e=>{t.forEach((t=>{t(e)}))}))},deregister:(...e)=>{const i=T(u);e.forEach((e=>{i.forEach((t=>{t(e)}))})),e.forEach((e=>{(e=>{if(!t.get(e.id))throw new Error(`Tried to deregister non-registered injectable "${e.id}".`);if([...o.entries()].filter((([,t])=>t.find((t=>t.injectable.id===e.id)))).map((e=>e[0])).forEach((e=>{o.delete(e),C.deregister(e)})),$(e),t.delete(e.id),e.injectionToken){const t=e.injectionToken.id;c.get(t).delete(e.id)}n.delete(e.id)})(e)}))},decorate:P,decorateFunction:(e,t)=>{P(e,(e=>(...n)=>t(e(...n))))},override:(e,i)=>{const o=t.get(e.id);if(!o)throw new Error(`Tried to override "${e.id}" which is not registered.`);if(r.has(e.id))throw new Error(`Tried to override injectable "${e.id}", but it was already injected.`);n.set(o.id,{...o,causesSideEffects:!1,instantiate:i})},unoverride:e=>{n.delete(e.id)},reset:()=>{n.clear()},preventSideEffects:()=>{i=!0},permitSideEffects:e=>{k(e).permitSideEffects()},purge:$},x={injectable:{id:e}};return{...C,inject:(e,t,n)=>C.inject(e,t,n?[x,n]:[x]),injectMany:(e,t,n)=>C.injectMany(e,t,n?[x,n]:[x])}},b=({getRelatedInjectables:e})=>t=>e(t)[0],f=({injectableMap:e,injectableIdsByInjectionToken:t})=>n=>{const i=t.get(n.id);return(i?[...i.values()]:[]).map((t=>e.get(t)))},g=({injectableMap:e,getInjectablesHavingInjectionToken:t})=>n=>{const i=e.get(n.id),r=t(n).filter((e=>e.id!==n.id));return i?[i,...r]:r},y=({di:e,injectable:t,instantiationParameter:n,context:i,instancesByInjectableMap:o,injectableAndRegistrationContext:a})=>{const c=[...i,{injectable:t,instantiationParameter:n}],d=o.get(t.id),s={inject:(t,n)=>e.inject(t,n,c),injectMany:(t,n)=>e.injectMany(t,n,c),context:c,register:(...t)=>(t.forEach((e=>{a.set(e,c)})),e.register(...t)),deregister:e.deregister},l=t.lifecycle.getInstanceKey(s,n),j=d.get(l);if(j)return j;const b=T({injectMany:e.injectMany,injectable:t})(t.instantiate)(s,...void 0===n?[]:[n]);return l!==r&&d.set(l,b),b},p=i({id:"registration-callback-token",decorable:!1}),u=i({id:"deregistration-callback-token",decorable:!1}),k=i({id:"instantiate-decorator-token",decorable:!1}),w=i({id:"injection-decorator-token",decorable:!1}),T=({injectMany:e,injectable:t})=>{const n=m(t);return i=>(...r)=>{if(!1===t.decorable)return i(...r);const[{context:o}]=r,a=e(k,void 0,o).filter(n).map((e=>e.decorate));return s(...a)(i)(...r)}},h=({injectMany:e})=>t=>(n,...i)=>{if(!1===n.decorable)return t(n,...i);const[,r]=i,o=r.filter((e=>!e.injectable.cannotCauseCycles)).find((e=>e.injectable.id===n.id)),a=[...r,{injectable:n}];if(o)throw new Error(`Cycle of injectables encountered: "${a.map((e=>e.injectable.id)).join('" -> "')}"`);const c=m(n),d=e(w,void 0,a).filter(c).map((e=>e.decorate));return s(...d)(t)(n,...i)},m=e=>t=>{return!t.target||(n=t.target,e=>e.id===n.id||e.injectionToken&&e.injectionToken.id===n.id)(e);var n},E=(e,t,n)=>{if(0===e.length){const e=[...n,{injectable:{id:t.id}}].map((e=>e.injectable.id)).join('" -> "');throw new Error(`Tried to inject non-registered injectable "${e}".`)}},M=(e,t)=>{if(e.length>1)throw new Error(`Tried to inject single injectable for injection token "${t.id}" but found multiple injectables: "${e.map((e=>e.id)).join('", "')}"`)},I=e=>e?.aliasType===c,S=e=>e?.aliasType===n;module.exports=t})(); | ||
(()=>{"use strict";var e={d:(t,n)=>{for(var i in n)e.o(n,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:n[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},t={};e.r(t),e.d(t,{createContainer:()=>j,deregistrationCallbackToken:()=>u,getInjectable:()=>d,getInjectionToken:()=>i,injectionDecoratorToken:()=>w,instantiationDecoratorToken:()=>m,isInjectable:()=>I,isInjectionToken:()=>v,lifecycleEnum:()=>a,registrationCallbackToken:()=>y});const n="injection-token",i=({id:e,decorable:t=!0})=>({id:e,aliasType:n,decorable:t}),r=Symbol("non-stored-instance-key"),o=Symbol("stored-instance-key"),a={singleton:{id:"singleton",getInstanceKey:()=>o},keyedSingleton:({getInstanceKey:e})=>({id:"keyedSingleton",getInstanceKey:e}),transient:{id:"transient",getInstanceKey:()=>r}},c="injectable",d=({lifecycle:e=a.singleton,...t})=>({aliasType:c,lifecycle:e,...t}),s=(...e)=>t=>{let n=t;return e.forEach((e=>{n=e(n)})),n},l=require("@ogre-tools/fp"),j=e=>{let t=new Map,n=new Map,i=!1,r=new Set;const o=new Map,a=new Map,c=new Map,s=b({injectableMap:t,injectableIdsByInjectionToken:c}),j=g({injectableMap:t,getInjectablesHavingInjectionToken:s}),m=f({getRelatedInjectables:j}),h=(e,t,n=[x])=>{const i=[...n,{injectable:e}],r=j(e).map((e=>I(e,t,i)));return r.find(l.isPromise)?Promise.all(r):r},T=k({injectMany:h}),I=T(((e,t,c=[])=>{const d=j(e);M(d,e),0===d.length&&!0===e.adHoc?C.register(e):E(d,e,c);const s=m(e);r.add(s.id);const l=n.get(s.id)||s;if(i&&l.causesSideEffects)throw new Error(`Tried to inject "${[...c,{injectable:l}].map((e=>e.injectable.id)).join('" -> "')}" when side-effects are prevented.`);return p({injectable:l,instantiationParameter:t,di:C,instancesByInjectableMap:a,context:c,injectableAndRegistrationContext:o})})),v=T(h),S=e=>{let n=e.id;if(!n)throw new Error("Tried to register injectable without ID.");if(t.has(n))throw new Error(`Tried to register multiple injectables for ID "${n}"`);const i={...e,permitSideEffects:function(){this.causesSideEffects=!1}};if(t.set(i.id,i),a.set(i.id,new Map),e.injectionToken){const t=e.injectionToken.id,i=c.get(t)||new Set;i.add(n),c.set(t,i)}},$=e=>{const t=m(e);a.get(t.id).clear()},P=(e,t)=>{const n=d({id:`${e.id}-decorator-${Math.random()}`,injectionToken:w,decorable:!1,instantiate:()=>({decorate:t,target:e})});S(n)},C={inject:I,injectMany:v,register:(...e)=>{e.forEach((e=>{S(e)}));const t=h(y);e.forEach((e=>{t.forEach((t=>{t(e)}))}))},deregister:(...e)=>{const i=h(u);e.forEach((e=>{i.forEach((t=>{t(e)}))})),e.forEach((e=>{(e=>{if(!t.get(e.id))throw new Error(`Tried to deregister non-registered injectable "${e.id}".`);if([...o.entries()].filter((([,t])=>t.find((t=>t.injectable.id===e.id)))).map((e=>e[0])).forEach((e=>{o.delete(e),C.deregister(e)})),$(e),t.delete(e.id),e.injectionToken){const t=e.injectionToken.id;c.get(t).delete(e.id)}n.delete(e.id)})(e)}))},decorate:P,decorateFunction:(e,t)=>{P(e,(e=>(...n)=>t(e(...n))))},override:(e,t)=>{const i=j(e);if(i.length>1)throw new Error(`Tried to override single implementation of injection token "${e.id}", but found multiple registered implementations: "${i.map((e=>e.id)).join('", "')}".`);if(0===i.length){if("injection-token"===e.aliasType)throw new Error(`Tried to override single implementation of injection token "${e.id}", but found no registered implementations.`);throw new Error(`Tried to override "${e.id}" which is not registered.`)}if(r.has(e.id))throw new Error(`Tried to override injectable "${e.id}", but it was already injected.`);const o=i[0];n.set(o.id,{...o,causesSideEffects:!1,instantiate:t})},unoverride:e=>{n.delete(e.id)},reset:()=>{n.clear()},preventSideEffects:()=>{i=!0},permitSideEffects:e=>{m(e).permitSideEffects()},purge:$},x={injectable:{id:e}};return{...C,inject:(e,t,n)=>C.inject(e,t,n?[x,n]:[x]),injectMany:(e,t,n)=>C.injectMany(e,t,n?[x,n]:[x])}},f=({getRelatedInjectables:e})=>t=>e(t)[0],b=({injectableMap:e,injectableIdsByInjectionToken:t})=>n=>{const i=t.get(n.id);return(i?[...i.values()]:[]).map((t=>e.get(t)))},g=({injectableMap:e,getInjectablesHavingInjectionToken:t})=>n=>{const i=e.get(n.id),r=t(n).filter((e=>e.id!==n.id));return i?[i,...r]:r},p=({di:e,injectable:t,instantiationParameter:n,context:i,instancesByInjectableMap:o,injectableAndRegistrationContext:a})=>{const c=[...i,{injectable:t,instantiationParameter:n}],d=o.get(t.id),s={inject:(t,n)=>e.inject(t,n,c),injectMany:(t,n)=>e.injectMany(t,n,c),context:c,register:(...t)=>(t.forEach((e=>{a.set(e,c)})),e.register(...t)),deregister:e.deregister},l=t.lifecycle.getInstanceKey(s,n),j=d.get(l);if(j)return j;const f=h({injectMany:e.injectMany,injectable:t})(t.instantiate)(s,...void 0===n?[]:[n]);return l!==r&&d.set(l,f),f},y=i({id:"registration-callback-token",decorable:!1}),u=i({id:"deregistration-callback-token",decorable:!1}),m=i({id:"instantiate-decorator-token",decorable:!1}),w=i({id:"injection-decorator-token",decorable:!1}),h=({injectMany:e,injectable:t})=>{const n=T(t);return i=>(...r)=>{if(!1===t.decorable)return i(...r);const[{context:o}]=r,a=e(m,void 0,o).filter(n).map((e=>e.decorate));return s(...a)(i)(...r)}},k=({injectMany:e})=>t=>(n,...i)=>{if(!1===n.decorable)return t(n,...i);const[,r]=i,o=r.filter((e=>!e.injectable.cannotCauseCycles)).find((e=>e.injectable.id===n.id)),a=[...r,{injectable:n}];if(o)throw new Error(`Cycle of injectables encountered: "${a.map((e=>e.injectable.id)).join('" -> "')}"`);const c=T(n),d=e(w,void 0,a).filter(c).map((e=>e.decorate));return s(...d)(t)(n,...i)},T=e=>t=>{return!t.target||(n=t.target,e=>e.id===n.id||e.injectionToken&&e.injectionToken.id===n.id)(e);var n},E=(e,t,n)=>{if(0===e.length){const e=[...n,{injectable:{id:t.id}}].map((e=>e.injectable.id)).join('" -> "');throw new Error(`Tried to inject non-registered injectable "${e}".`)}},M=(e,t)=>{if(e.length>1)throw new Error(`Tried to inject single injectable for injection token "${t.id}" but found multiple injectables: "${e.map((e=>e.id)).join('", "')}"`)},I=e=>e?.aliasType===c,v=e=>e?.aliasType===n;module.exports=t})(); |
@@ -6,2 +6,13 @@ # Change Log | ||
## [14.0.0](https://github.com/ogre-works/ogre-tools/compare/v13.2.1...v14.0.0) (2022-11-22) | ||
### ⚠ BREAKING CHANGES | ||
- Make properties of an injectable read-only to prevent misuse | ||
### Features | ||
- Make properties of an injectable read-only to prevent misuse ([abf402a](https://github.com/ogre-works/ogre-tools/commit/abf402a2c3edaa9701d818c926c9faa3d029779e)) | ||
- Permit override of single injectable using injection token ([f6ae117](https://github.com/ogre-works/ogre-tools/commit/f6ae117a949c353267ccd21bb7b5239705c6a62e)) | ||
### [13.2.1](https://github.com/ogre-works/ogre-tools/compare/v13.2.0...v13.2.1) (2022-11-17) | ||
@@ -8,0 +19,0 @@ |
@@ -12,7 +12,9 @@ /// <reference types="jest" /> | ||
>( | ||
injectable: Injectable< | ||
InjectionInstance, | ||
InjectionTokenInstance, | ||
InstantiationParam | ||
>, | ||
injectable: | ||
| InjectionToken<InjectionInstance, InstantiationParam> | ||
| Injectable< | ||
InjectionInstance, | ||
InjectionTokenInstance, | ||
InstantiationParam | ||
>, | ||
instantiateStub: Instantiate<InjectionInstance, InstantiationParam>, | ||
@@ -48,9 +50,12 @@ ): void; | ||
> { | ||
id: string; | ||
causesSideEffects?: boolean; | ||
injectionToken?: InjectionToken<InjectionTokenInstance, InstantiationParam>; | ||
instantiate: Instantiate<InjectionInstance, InstantiationParam>; | ||
lifecycle: ILifecycle<InstantiationParam>; | ||
decorable?: boolean; | ||
tags?: any[]; | ||
readonly id: string; | ||
readonly causesSideEffects?: boolean; | ||
readonly injectionToken?: InjectionToken< | ||
InjectionTokenInstance, | ||
InstantiationParam | ||
>; | ||
readonly instantiate: Instantiate<InjectionInstance, InstantiationParam>; | ||
readonly lifecycle: ILifecycle<InstantiationParam>; | ||
readonly decorable?: boolean; | ||
readonly tags?: any[]; | ||
} | ||
@@ -57,0 +62,0 @@ |
@@ -168,5 +168,10 @@ import { expectError, expectNotType, expectType } from 'tsd'; | ||
const someOtherInjectionToken = getInjectionToken<{ someProperty: number }>({ | ||
id: 'some-other-injection-token', | ||
}); | ||
const someInjectableForOverrides = getInjectable({ | ||
id: 'some-injectable', | ||
instantiate: () => ({ someProperty: 42 }), | ||
injectionToken: someOtherInjectionToken, | ||
}); | ||
@@ -193,1 +198,13 @@ | ||
); | ||
// given injectable with injection token, when overriding with injection token, typing is OK | ||
expectType<void>( | ||
di.override(someOtherInjectionToken, () => ({ someProperty: 84 })), | ||
); | ||
// given injectable with injection token, when overriding with injection token, but wrong type of override, typing is not OK | ||
expectError( | ||
di.override(someOtherInjectionToken, () => ({ | ||
someProperty: 'not a number', | ||
})), | ||
); |
{ | ||
"name": "@ogre-tools/injectable", | ||
"private": false, | ||
"version": "13.2.1", | ||
"version": "14.0.0", | ||
"description": "A brutal dependency injection container", | ||
@@ -22,3 +22,3 @@ "repository": { | ||
}, | ||
"gitHead": "5889425b808b61ca136430236ac1c810b1aad420", | ||
"gitHead": "dd2fe8ff5373e02f13c5fe9d3a407ca1e397e36c", | ||
"bugs": { | ||
@@ -38,8 +38,8 @@ "url": "https://github.com/ogre-works/ogre-tools/issues" | ||
"devDependencies": { | ||
"@ogre-tools/infrastructure-babel-for-js": "^13.2.1", | ||
"@ogre-tools/infrastructure-jest": "^13.2.1", | ||
"@ogre-tools/infrastructure-prettier": "^13.2.1", | ||
"@ogre-tools/infrastructure-webpack-for-js": "^13.2.1" | ||
"@ogre-tools/infrastructure-babel-for-js": "^14.0.0", | ||
"@ogre-tools/infrastructure-jest": "^14.0.0", | ||
"@ogre-tools/infrastructure-prettier": "^14.0.0", | ||
"@ogre-tools/infrastructure-webpack-for-js": "^14.0.0" | ||
}, | ||
"prettier": "@ogre-tools/infrastructure-prettier" | ||
} |
@@ -241,6 +241,22 @@ import flow from './fastFlow'; | ||
override: (alias, instantiateStub) => { | ||
const originalInjectable = injectableMap.get(alias.id); | ||
const relatedInjectables = getRelatedInjectables(alias); | ||
if (!originalInjectable) { | ||
if (relatedInjectables.length > 1) { | ||
throw new Error( | ||
`Tried to override single implementation of injection token "${ | ||
alias.id | ||
}", but found multiple registered implementations: "${relatedInjectables | ||
.map(x => x.id) | ||
.join('", "')}".`, | ||
); | ||
} | ||
if (relatedInjectables.length === 0) { | ||
if (alias.aliasType === 'injection-token') { | ||
throw new Error( | ||
`Tried to override single implementation of injection token "${alias.id}", but found no registered implementations.`, | ||
); | ||
} | ||
throw new Error( | ||
`Tried to override "${alias.id}" which is not registered.`, | ||
@@ -256,2 +272,4 @@ ); | ||
const originalInjectable = relatedInjectables[0]; | ||
overridingInjectables.set(originalInjectable.id, { | ||
@@ -258,0 +276,0 @@ ...originalInjectable, |
@@ -206,2 +206,88 @@ import lifecycleEnum from './lifecycleEnum'; | ||
}); | ||
it('given single registered injectable with injection token, and the injection token is overridden, when the injection token is inject-singled, injects the overridden instance', () => { | ||
const di = createContainer('some-container'); | ||
const someInjectionToken = getInjectionToken({ | ||
id: 'some-injection-token', | ||
}); | ||
const injectable = getInjectable({ | ||
id: 'some-injectable', | ||
instantiate: () => 'irrelevant', | ||
injectionToken: someInjectionToken, | ||
}); | ||
di.register(injectable); | ||
di.override(someInjectionToken, () => 'some-override'); | ||
const actual = di.inject(someInjectionToken); | ||
expect(actual).toBe('some-override'); | ||
}); | ||
it('given single registered injectable with injection token, and the injection token is overridden, when the injection token is inject-many, injects the overridden instance', () => { | ||
const di = createContainer('some-container'); | ||
const someInjectionToken = getInjectionToken({ | ||
id: 'some-injection-token', | ||
}); | ||
const injectable = getInjectable({ | ||
id: 'some-injectable', | ||
instantiate: () => 'irrelevant', | ||
injectionToken: someInjectionToken, | ||
}); | ||
di.register(injectable); | ||
di.override(someInjectionToken, () => 'some-override'); | ||
const actual = di.injectMany(someInjectionToken); | ||
expect(actual).toEqual(['some-override']); | ||
}); | ||
it('given multiple registered injectables with injection token, and the injection token is overridden, throws', () => { | ||
const di = createContainer('some-container'); | ||
const someInjectionToken = getInjectionToken({ | ||
id: 'some-injection-token', | ||
}); | ||
const injectable1 = getInjectable({ | ||
id: 'some-injectable-1', | ||
instantiate: () => 'irrelevant', | ||
injectionToken: someInjectionToken, | ||
}); | ||
const injectable2 = getInjectable({ | ||
id: 'some-injectable-2', | ||
instantiate: () => 'irrelevant', | ||
injectionToken: someInjectionToken, | ||
}); | ||
di.register(injectable1, injectable2); | ||
expect(() => { | ||
di.override(someInjectionToken, () => 'some-override'); | ||
}).toThrow( | ||
'Tried to override single implementation of injection token "some-injection-token", but found multiple registered implementations: "some-injectable-1", "some-injectable-2".', | ||
); | ||
}); | ||
it('given no registered injectable with injection token, when the injection token is overridden, throws', () => { | ||
const di = createContainer('some-container'); | ||
const someInjectionToken = getInjectionToken({ | ||
id: 'some-injection-token', | ||
}); | ||
expect(() => { | ||
di.override(someInjectionToken, () => 'some-override'); | ||
}).toThrow( | ||
'Tried to override single implementation of injection token "some-injection-token", but found no registered implementations.', | ||
); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
305547
3725